@raindrop-ai/claude-code 0.0.13 → 0.0.15

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 CHANGED
@@ -1,4 +1,4 @@
1
- // ../core/dist/chunk-4UCYIEH4.js
1
+ // ../core/dist/chunk-SK6EJEO7.js
2
2
  function getCrypto() {
3
3
  const c = globalThis.crypto;
4
4
  return c;
@@ -42,6 +42,8 @@ function base64Encode(bytes) {
42
42
  }
43
43
  return out;
44
44
  }
45
+ var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
46
+ var MAX_RETRY_DELAY_MS = 3e4;
45
47
  function wait(ms) {
46
48
  return new Promise((resolve) => setTimeout(resolve, ms));
47
49
  }
@@ -49,6 +51,46 @@ function formatEndpoint(endpoint) {
49
51
  if (!endpoint) return void 0;
50
52
  return endpoint.endsWith("/") ? endpoint : `${endpoint}/`;
51
53
  }
54
+ function redactUrlForLog(url) {
55
+ try {
56
+ const parsed = new URL(url);
57
+ parsed.username = "";
58
+ parsed.password = "";
59
+ parsed.search = "";
60
+ parsed.hash = "";
61
+ return parsed.toString();
62
+ } catch (e) {
63
+ return "<unparseable-url>";
64
+ }
65
+ }
66
+ var RATE_LIMITED_LOG_INTERVAL_MS = 3e4;
67
+ var rateLimitedLogLast = /* @__PURE__ */ new Map();
68
+ function rateLimitedLog(key, log) {
69
+ const now = Date.now();
70
+ const last = rateLimitedLogLast.get(key);
71
+ if (last !== void 0 && now - last < RATE_LIMITED_LOG_INTERVAL_MS) {
72
+ return false;
73
+ }
74
+ rateLimitedLogLast.set(key, now);
75
+ log();
76
+ return true;
77
+ }
78
+ async function raceWithTimeout(promise, timeoutMs) {
79
+ let timer;
80
+ const settledInTime = await Promise.race([
81
+ promise.then(
82
+ () => true,
83
+ () => true
84
+ ),
85
+ new Promise((resolve) => {
86
+ var _a;
87
+ timer = setTimeout(() => resolve(false), Math.max(0, timeoutMs));
88
+ (_a = timer.unref) == null ? void 0 : _a.call(timer);
89
+ })
90
+ ]);
91
+ if (timer) clearTimeout(timer);
92
+ return settledInTime;
93
+ }
52
94
  function parseRetryAfter(headers) {
53
95
  var _a;
54
96
  const value = (_a = headers.get("Retry-After")) != null ? _a : headers.get("retry-after");
@@ -65,12 +107,12 @@ function parseRetryAfter(headers) {
65
107
  function getRetryDelayMs(attemptNumber, previousError) {
66
108
  if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
67
109
  const v = previousError.retryAfterMs;
68
- if (typeof v === "number") return Math.max(0, v);
110
+ if (typeof v === "number") return Math.min(Math.max(0, v), MAX_RETRY_DELAY_MS);
69
111
  }
70
112
  if (attemptNumber <= 1) return 0;
71
113
  const base = 500;
72
114
  const factor = Math.pow(2, attemptNumber - 2);
73
- return base * factor;
115
+ return Math.min(base * factor, MAX_RETRY_DELAY_MS);
74
116
  }
75
117
  async function withRetry(operation, opName, opts) {
76
118
  const prefix = opts.sdkName ? `[raindrop-ai/${opts.sdkName}]` : "[raindrop-ai/core]";
@@ -105,7 +147,9 @@ async function withRetry(operation, opName, opts) {
105
147
  throw lastError instanceof Error ? lastError : new Error(String(lastError));
106
148
  }
107
149
  async function postJson(url, body, headers, opts) {
108
- const opName = `POST ${url}`;
150
+ var _a;
151
+ const opName = `POST ${redactUrlForLog(url)}`;
152
+ const timeoutMs = (_a = opts.timeoutMs) != null ? _a : DEFAULT_REQUEST_TIMEOUT_MS;
109
153
  await withRetry(
110
154
  async () => {
111
155
  const resp = await fetch(url, {
@@ -114,7 +158,8 @@ async function postJson(url, body, headers, opts) {
114
158
  "Content-Type": "application/json",
115
159
  ...headers
116
160
  },
117
- body: JSON.stringify(body)
161
+ body: JSON.stringify(body),
162
+ signal: AbortSignal.timeout(timeoutMs)
118
163
  });
119
164
  if (!resp.ok) {
120
165
  const text = await resp.text().catch(() => "");
@@ -131,6 +176,27 @@ async function postJson(url, body, headers, opts) {
131
176
  opts
132
177
  );
133
178
  }
179
+ var DEFAULT_MAX_TEXT_FIELD_CHARS = 1e6;
180
+ var TRUNCATION_MARKER = "...[truncated by raindrop]";
181
+ var currentDefaultMaxTextFieldChars = DEFAULT_MAX_TEXT_FIELD_CHARS;
182
+ function resolveMaxTextFieldChars(value) {
183
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
184
+ return Math.floor(value);
185
+ }
186
+ return currentDefaultMaxTextFieldChars;
187
+ }
188
+ function truncateToLimit(text, limit) {
189
+ if (limit > TRUNCATION_MARKER.length) {
190
+ return text.slice(0, limit - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;
191
+ }
192
+ return text.slice(0, Math.max(0, limit));
193
+ }
194
+ function capText(value, limit) {
195
+ if (typeof value !== "string") return value;
196
+ const max = limit != null ? limit : currentDefaultMaxTextFieldChars;
197
+ if (value.length <= max) return value;
198
+ return truncateToLimit(value, max);
199
+ }
134
200
  var SpanStatusCode = {
135
201
  UNSET: 0,
136
202
  OK: 1,
@@ -188,6 +254,112 @@ function buildExportTraceServiceRequest(spans, serviceName = "raindrop.core", se
188
254
  ]
189
255
  };
190
256
  }
257
+ var LOCAL_DEBUGGER_ENV_VAR = "RAINDROP_LOCAL_DEBUGGER";
258
+ var WORKSHOP_ENV_VAR = "RAINDROP_WORKSHOP";
259
+ var DEFAULT_LOCAL_WORKSHOP_URL = "http://localhost:5899/v1/";
260
+ function readEnvVar(name) {
261
+ var _a;
262
+ try {
263
+ const env = (_a = globalThis == null ? void 0 : globalThis.process) == null ? void 0 : _a.env;
264
+ if (env && typeof env[name] === "string" && env[name].length > 0) {
265
+ return env[name];
266
+ }
267
+ } catch (e) {
268
+ }
269
+ return void 0;
270
+ }
271
+ function readWorkshopEnv() {
272
+ const raw = readEnvVar(WORKSHOP_ENV_VAR);
273
+ if (raw === void 0) return void 0;
274
+ const trimmed = raw.trim();
275
+ if (trimmed.length === 0) return void 0;
276
+ if (/^https?:\/\//i.test(trimmed)) return { url: trimmed };
277
+ if (/^(1|true|yes|on)$/i.test(trimmed)) return "enable";
278
+ if (/^(0|false|no|off)$/i.test(trimmed)) return "disable";
279
+ return void 0;
280
+ }
281
+ function isLocalDevHost(hostname) {
282
+ if (!hostname) return false;
283
+ if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "0.0.0.0" || hostname === "::1") {
284
+ return true;
285
+ }
286
+ if (hostname.endsWith(".localhost")) return true;
287
+ return false;
288
+ }
289
+ function readRuntimeHostname() {
290
+ try {
291
+ const loc = globalThis == null ? void 0 : globalThis.location;
292
+ if (loc && typeof loc.hostname === "string" && loc.hostname.length > 0) {
293
+ return loc.hostname;
294
+ }
295
+ } catch (e) {
296
+ }
297
+ return void 0;
298
+ }
299
+ function shouldAutoEnableLocalWorkshop() {
300
+ if (isLocalDevHost(readRuntimeHostname())) return true;
301
+ if (readEnvVar("NODE_ENV") === "development") return true;
302
+ return false;
303
+ }
304
+ function resolveLocalDebuggerBaseUrl(baseUrl) {
305
+ var _a, _b, _c;
306
+ if (baseUrl === null) return null;
307
+ if (typeof baseUrl === "string" && baseUrl.length > 0) {
308
+ return (_a = formatEndpoint(baseUrl)) != null ? _a : null;
309
+ }
310
+ const explicitUrlEnv = readEnvVar(LOCAL_DEBUGGER_ENV_VAR);
311
+ if (explicitUrlEnv) return (_b = formatEndpoint(explicitUrlEnv)) != null ? _b : null;
312
+ const workshopEnv = readWorkshopEnv();
313
+ if (workshopEnv === "disable") return null;
314
+ if (workshopEnv === "enable") return DEFAULT_LOCAL_WORKSHOP_URL;
315
+ if (workshopEnv && "url" in workshopEnv) return (_c = formatEndpoint(workshopEnv.url)) != null ? _c : null;
316
+ if (shouldAutoEnableLocalWorkshop()) return DEFAULT_LOCAL_WORKSHOP_URL;
317
+ return null;
318
+ }
319
+ function mirrorTraceExportToLocalDebugger(body, options = {}) {
320
+ var _a;
321
+ const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
322
+ if (!baseUrl) return;
323
+ void postJson(`${baseUrl}traces`, body, {}, {
324
+ maxAttempts: 1,
325
+ debug: (_a = options.debug) != null ? _a : false,
326
+ sdkName: options.sdkName
327
+ }).catch(() => {
328
+ });
329
+ }
330
+ function mirrorPartialEventToLocalDebugger(event, options = {}) {
331
+ var _a;
332
+ const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
333
+ if (!baseUrl) return;
334
+ const headers = options.writeKey ? { Authorization: `Bearer ${options.writeKey}` } : {};
335
+ void postJson(`${baseUrl}events/track_partial`, event, headers, {
336
+ maxAttempts: 1,
337
+ debug: (_a = options.debug) != null ? _a : false,
338
+ sdkName: options.sdkName
339
+ }).catch(() => {
340
+ });
341
+ }
342
+ var PROJECT_ID_HEADER = "X-Raindrop-Project-Id";
343
+ var PROJECT_ID_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
344
+ function isValidProjectIdSlug(value) {
345
+ return PROJECT_ID_SLUG_PATTERN.test(value);
346
+ }
347
+ function normalizeProjectId(raw, opts) {
348
+ if (typeof raw !== "string") return void 0;
349
+ const trimmed = raw.trim();
350
+ if (!trimmed) return void 0;
351
+ if (!isValidProjectIdSlug(trimmed) && opts.debug) {
352
+ console.warn(
353
+ `${opts.prefix} projectId "${trimmed}" does not match slug ${PROJECT_ID_SLUG_PATTERN.source}; sending anyway \u2014 backend may reject with HTTP 400`
354
+ );
355
+ }
356
+ return trimmed;
357
+ }
358
+ function projectIdHeaders(projectId) {
359
+ return projectId ? { [PROJECT_ID_HEADER]: projectId } : {};
360
+ }
361
+ var SHUTDOWN_DEADLINE_MS = 1e4;
362
+ var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
191
363
  function mergePatches(target, source) {
192
364
  var _a, _b, _c, _d;
193
365
  const out = { ...target, ...source };
@@ -205,7 +377,8 @@ var EventShipper = class {
205
377
  this.sticky = /* @__PURE__ */ new Map();
206
378
  this.timers = /* @__PURE__ */ new Map();
207
379
  this.inFlight = /* @__PURE__ */ new Set();
208
- var _a, _b, _c, _d, _e, _f, _g;
380
+ this.hasShutdown = false;
381
+ var _a, _b, _c, _d, _e, _f, _g, _h;
209
382
  this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
210
383
  this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
211
384
  this.enabled = opts.enabled !== false;
@@ -214,11 +387,20 @@ var EventShipper = class {
214
387
  this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
215
388
  this.prefix = `[raindrop-ai/${this.sdkName}]`;
216
389
  this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
390
+ this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
391
+ this.localDebuggerUrl = (_f = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _f : void 0;
392
+ if (this.debug && this.localDebuggerUrl) {
393
+ console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
394
+ }
395
+ this.projectId = normalizeProjectId(opts.projectId, {
396
+ debug: this.debug,
397
+ prefix: this.prefix
398
+ });
217
399
  const isNode = typeof process !== "undefined" && typeof process.version === "string";
218
400
  this.context = {
219
401
  library: {
220
- name: (_f = opts.libraryName) != null ? _f : "@raindrop-ai/core",
221
- version: (_g = opts.libraryVersion) != null ? _g : "0.0.0"
402
+ name: (_g = opts.libraryName) != null ? _g : "@raindrop-ai/core",
403
+ version: (_h = opts.libraryVersion) != null ? _h : "0.0.0"
222
404
  },
223
405
  metadata: {
224
406
  jsRuntime: isNode ? "node" : "web",
@@ -232,10 +414,55 @@ var EventShipper = class {
232
414
  authHeaders() {
233
415
  return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
234
416
  }
417
+ requestHeaders() {
418
+ return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
419
+ }
420
+ /**
421
+ * Build the retry/timeout options for one POST, honoring the shutdown
422
+ * deadline. Returns `null` when the shutdown drain window is exhausted —
423
+ * the caller must drop the payload (with a rate-limited warning) instead
424
+ * of issuing a request that could outlive process exit.
425
+ *
426
+ * Checked fresh on EVERY send, so a shutdown that begins while the flush
427
+ * path is mid-drain takes effect immediately: no further retries, and the
428
+ * per-attempt timeout is clamped to the remaining window. After
429
+ * `shutdown()` returns (deadline cleared, `hasShutdown` still set),
430
+ * sends — late callers, or flush work the deadline abandoned mid-drain —
431
+ * run as a single short attempt rather than regaining the full retry
432
+ * schedule.
433
+ */
434
+ requestOpts() {
435
+ if (this.shutdownDeadlineAt !== void 0) {
436
+ const remainingMs = this.shutdownDeadlineAt - Date.now();
437
+ if (remainingMs <= 0) return null;
438
+ return {
439
+ maxAttempts: 1,
440
+ debug: this.debug,
441
+ sdkName: this.sdkName,
442
+ timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
443
+ };
444
+ }
445
+ if (this.hasShutdown) {
446
+ return {
447
+ maxAttempts: 1,
448
+ debug: this.debug,
449
+ sdkName: this.sdkName,
450
+ timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
451
+ };
452
+ }
453
+ return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
454
+ }
235
455
  async patch(eventId, patch) {
236
456
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
237
457
  if (!this.enabled) return;
238
458
  if (!eventId || !eventId.trim()) return;
459
+ const maxChars = resolveMaxTextFieldChars(this.maxTextFieldCharsOpt);
460
+ if (typeof patch.input === "string" && patch.input.length > maxChars) {
461
+ patch = { ...patch, input: capText(patch.input, maxChars) };
462
+ }
463
+ if (typeof patch.output === "string" && patch.output.length > maxChars) {
464
+ patch = { ...patch, output: capText(patch.output, maxChars) };
465
+ }
239
466
  if (this.debug) {
240
467
  console.log(`${this.prefix} queue patch`, {
241
468
  eventId,
@@ -282,9 +509,16 @@ var EventShipper = class {
282
509
  })));
283
510
  }
284
511
  async shutdown() {
285
- for (const t of this.timers.values()) clearTimeout(t);
286
- this.timers.clear();
287
- await this.flush();
512
+ this.hasShutdown = true;
513
+ this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
514
+ try {
515
+ for (const t of this.timers.values()) clearTimeout(t);
516
+ this.timers.clear();
517
+ const settled = await raceWithTimeout(this.flush(), SHUTDOWN_DEADLINE_MS);
518
+ if (!settled) this.warnShutdownDrop("in-flight request(s) at shutdown");
519
+ } finally {
520
+ this.shutdownDeadlineAt = void 0;
521
+ }
288
522
  }
289
523
  async trackSignal(signal) {
290
524
  var _a, _b;
@@ -304,16 +538,21 @@ var EventShipper = class {
304
538
  }
305
539
  }
306
540
  ];
541
+ if (!this.writeKey) return;
307
542
  const url = `${this.baseUrl}signals/track`;
543
+ const opts = this.requestOpts();
544
+ if (!opts) {
545
+ this.warnShutdownDrop("signal");
546
+ return;
547
+ }
308
548
  try {
309
- await postJson(url, body, this.authHeaders(), {
310
- maxAttempts: 3,
311
- debug: this.debug,
312
- sdkName: this.sdkName
313
- });
549
+ await postJson(url, body, this.requestHeaders(), opts);
314
550
  } catch (err) {
315
551
  const msg = err instanceof Error ? err.message : String(err);
316
- console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`);
552
+ rateLimitedLog(
553
+ `${this.prefix}.send_signal_failed`,
554
+ () => console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`)
555
+ );
317
556
  }
318
557
  }
319
558
  async identify(users) {
@@ -334,19 +573,30 @@ var EventShipper = class {
334
573
  traits: (_a = user.traits) != null ? _a : {}
335
574
  };
336
575
  });
576
+ if (!this.writeKey) return;
337
577
  if (body.length === 0) return;
338
578
  const url = `${this.baseUrl}users/identify`;
579
+ const opts = this.requestOpts();
580
+ if (!opts) {
581
+ this.warnShutdownDrop("identify");
582
+ return;
583
+ }
339
584
  try {
340
- await postJson(url, body, this.authHeaders(), {
341
- maxAttempts: 3,
342
- debug: this.debug,
343
- sdkName: this.sdkName
344
- });
585
+ await postJson(url, body, this.requestHeaders(), opts);
345
586
  } catch (err) {
346
587
  const msg = err instanceof Error ? err.message : String(err);
347
- console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`);
588
+ rateLimitedLog(
589
+ `${this.prefix}.send_identify_failed`,
590
+ () => console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`)
591
+ );
348
592
  }
349
593
  }
594
+ warnShutdownDrop(what) {
595
+ rateLimitedLog(
596
+ `${this.prefix}.shutdown_deadline`,
597
+ () => console.warn(`${this.prefix} shutdown flush deadline exceeded; dropping ${what}`)
598
+ );
599
+ }
350
600
  async flushOne(eventId) {
351
601
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
352
602
  if (!this.enabled) return;
@@ -410,11 +660,25 @@ var EventShipper = class {
410
660
  endpoint: url
411
661
  });
412
662
  }
413
- const p = postJson(url, payload, this.authHeaders(), {
414
- maxAttempts: 3,
415
- debug: this.debug,
416
- sdkName: this.sdkName
417
- });
663
+ if (this.localDebuggerUrl) {
664
+ mirrorPartialEventToLocalDebugger(payload, {
665
+ baseUrl: this.localDebuggerUrl,
666
+ writeKey: this.writeKey,
667
+ debug: this.debug,
668
+ sdkName: this.sdkName
669
+ });
670
+ }
671
+ if (!this.writeKey) {
672
+ if (!isPending) this.sticky.delete(eventId);
673
+ return;
674
+ }
675
+ const opts = this.requestOpts();
676
+ if (!opts) {
677
+ this.warnShutdownDrop(`track_partial ${eventId}`);
678
+ if (!isPending) this.sticky.delete(eventId);
679
+ return;
680
+ }
681
+ const p = postJson(url, payload, this.requestHeaders(), opts);
418
682
  this.inFlight.add(p);
419
683
  try {
420
684
  try {
@@ -424,7 +688,10 @@ var EventShipper = class {
424
688
  }
425
689
  } catch (err) {
426
690
  const msg = err instanceof Error ? err.message : String(err);
427
- console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`);
691
+ rateLimitedLog(
692
+ `${this.prefix}.send_track_partial_failed`,
693
+ () => console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`)
694
+ );
428
695
  }
429
696
  } finally {
430
697
  this.inFlight.delete(p);
@@ -434,28 +701,114 @@ var EventShipper = class {
434
701
  }
435
702
  }
436
703
  };
437
- var LOCAL_DEBUGGER_ENV_VAR = "RAINDROP_LOCAL_DEBUGGER";
438
- function resolveLocalDebuggerBaseUrl(baseUrl) {
439
- var _a, _b, _c;
440
- const resolved = (_b = baseUrl != null ? baseUrl : typeof process !== "undefined" ? (_a = process.env) == null ? void 0 : _a[LOCAL_DEBUGGER_ENV_VAR] : void 0) != null ? _b : null;
441
- return resolved ? (_c = formatEndpoint(resolved)) != null ? _c : null : null;
704
+ var DEFAULT_SECRET_KEY_NAMES = [
705
+ "apikey",
706
+ "apisecret",
707
+ "apitoken",
708
+ "secretaccesskey",
709
+ "sessiontoken",
710
+ "privatekey",
711
+ "privatekeyid",
712
+ "clientsecret",
713
+ "accesstoken",
714
+ "refreshtoken",
715
+ "oauthtoken",
716
+ "bearertoken",
717
+ "authorization",
718
+ "password",
719
+ "passphrase"
720
+ ];
721
+ var REDACTED_PLACEHOLDER = "[REDACTED]";
722
+ function normalizeKeyName(name) {
723
+ return name.toLowerCase().replace(/[-_.]/g, "");
724
+ }
725
+ function redactSecretsInObject(value, options) {
726
+ var _a, _b;
727
+ const normalizedSecretSet = buildSecretSet((_a = options == null ? void 0 : options.secretKeyNames) != null ? _a : DEFAULT_SECRET_KEY_NAMES);
728
+ const placeholder = (_b = options == null ? void 0 : options.placeholder) != null ? _b : REDACTED_PLACEHOLDER;
729
+ const seen = /* @__PURE__ */ new WeakSet();
730
+ const walk = (node) => {
731
+ if (node === null || typeof node !== "object") return node;
732
+ if (seen.has(node)) return "[CIRCULAR]";
733
+ seen.add(node);
734
+ if (Array.isArray(node)) {
735
+ return node.map((item) => walk(item));
736
+ }
737
+ const out = {};
738
+ for (const [k, v] of Object.entries(node)) {
739
+ if (normalizedSecretSet.has(normalizeKeyName(k))) {
740
+ out[k] = placeholder;
741
+ } else {
742
+ out[k] = walk(v);
743
+ }
744
+ }
745
+ return out;
746
+ };
747
+ return walk(value);
748
+ }
749
+ function buildSecretSet(names) {
750
+ const set = /* @__PURE__ */ new Set();
751
+ for (const name of names) set.add(normalizeKeyName(name));
752
+ return set;
753
+ }
754
+ var DEFAULT_REDACT_ATTRIBUTE_KEYS = [
755
+ "ai.request.providerOptions",
756
+ "ai.response.providerMetadata"
757
+ ];
758
+ function defaultTransformSpan(span) {
759
+ const attrs = span.attributes;
760
+ if (!attrs || attrs.length === 0) return span;
761
+ let nextAttrs;
762
+ for (let i = 0; i < attrs.length; i++) {
763
+ const attr = attrs[i];
764
+ const redacted = redactJsonAttributeValue(attr.key, attr.value);
765
+ if (redacted === void 0) continue;
766
+ if (!nextAttrs) nextAttrs = attrs.slice();
767
+ nextAttrs[i] = { key: attr.key, value: redacted };
768
+ }
769
+ if (!nextAttrs) return span;
770
+ return { ...span, attributes: nextAttrs };
771
+ }
772
+ var REDACT_JSON_ATTRIBUTE_KEYS = new Set(DEFAULT_REDACT_ATTRIBUTE_KEYS);
773
+ function redactJsonAttributeValue(key, value) {
774
+ if (!REDACT_JSON_ATTRIBUTE_KEYS.has(key)) return void 0;
775
+ const json = value.stringValue;
776
+ if (typeof json !== "string" || json.length === 0) return void 0;
777
+ let parsed;
778
+ try {
779
+ parsed = JSON.parse(json);
780
+ } catch (e) {
781
+ return void 0;
782
+ }
783
+ const scrubbed = redactSecretsInObject(parsed);
784
+ let scrubbedJson;
785
+ try {
786
+ scrubbedJson = JSON.stringify(scrubbed);
787
+ } catch (e) {
788
+ return void 0;
789
+ }
790
+ if (scrubbedJson === json) return void 0;
791
+ return { stringValue: scrubbedJson };
442
792
  }
443
- function mirrorTraceExportToLocalDebugger(body, options = {}) {
444
- var _a;
445
- const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
446
- if (!baseUrl) return;
447
- void postJson(`${baseUrl}traces`, body, {}, {
448
- maxAttempts: 1,
449
- debug: (_a = options.debug) != null ? _a : false,
450
- sdkName: options.sdkName
451
- }).catch(() => {
452
- });
793
+ function applyOtelSpanAttributeLimit(limit) {
794
+ var _a, _b;
795
+ try {
796
+ 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;
797
+ if (!raw) return limit;
798
+ const parsed = Number.parseInt(raw, 10);
799
+ if (Number.isFinite(parsed) && parsed > 0) {
800
+ return Math.min(limit, parsed);
801
+ }
802
+ } catch (e) {
803
+ }
804
+ return limit;
453
805
  }
454
806
  var TraceShipper = class {
455
807
  constructor(opts) {
456
808
  this.queue = [];
457
809
  this.inFlight = /* @__PURE__ */ new Set();
458
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
810
+ this.hasShutdown = false;
811
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
459
812
  this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
460
813
  this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
461
814
  this.enabled = opts.enabled !== false;
@@ -468,13 +821,83 @@ var TraceShipper = class {
468
821
  this.prefix = `[raindrop-ai/${this.sdkName}]`;
469
822
  this.serviceName = (_g = opts.serviceName) != null ? _g : "raindrop.core";
470
823
  this.serviceVersion = (_h = opts.serviceVersion) != null ? _h : "0.0.0";
471
- const localDebugger = typeof process !== "undefined" ? (_i = process.env) == null ? void 0 : _i.RAINDROP_LOCAL_DEBUGGER : void 0;
472
- if (localDebugger) {
473
- this.localDebuggerUrl = (_j = resolveLocalDebuggerBaseUrl(localDebugger)) != null ? _j : void 0;
474
- if (this.debug) {
475
- console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
824
+ this.localDebuggerUrl = (_i = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _i : void 0;
825
+ if (this.debug && this.localDebuggerUrl) {
826
+ console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
827
+ }
828
+ this.projectId = normalizeProjectId(opts.projectId, {
829
+ debug: this.debug,
830
+ prefix: this.prefix
831
+ });
832
+ this.transformSpanHook = opts.transformSpan;
833
+ this.disableDefaultRedaction = opts.disableDefaultRedaction === true;
834
+ this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
835
+ }
836
+ /**
837
+ * Cap every string attribute value on the span. O(#attributes) length
838
+ * checks; only oversized values pay a slice. Runs AFTER the redaction
839
+ * pipeline so the default secret-scrub still sees parseable JSON in
840
+ * `ai.request.providerOptions` / `ai.response.providerMetadata` (capping
841
+ * first could cut a JSON blob mid-way, fail the parse, and ship secrets
842
+ * in the surviving prefix).
843
+ *
844
+ * A stricter `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var is honored
845
+ * for span content, matching the Python SDK and the OTel SDK convention.
846
+ */
847
+ capSpanAttributes(span) {
848
+ var _a;
849
+ const maxChars = applyOtelSpanAttributeLimit(
850
+ resolveMaxTextFieldChars(this.maxTextFieldCharsOpt)
851
+ );
852
+ const attrs = span.attributes;
853
+ if (!attrs || attrs.length === 0) return span;
854
+ let nextAttrs;
855
+ for (let i = 0; i < attrs.length; i++) {
856
+ const attr = attrs[i];
857
+ const value = (_a = attr.value) == null ? void 0 : _a.stringValue;
858
+ if (typeof value !== "string" || value.length <= maxChars) continue;
859
+ if (!nextAttrs) nextAttrs = attrs.slice();
860
+ nextAttrs[i] = {
861
+ key: attr.key,
862
+ value: { ...attr.value, stringValue: capText(value, maxChars) }
863
+ };
864
+ }
865
+ if (!nextAttrs) return span;
866
+ return { ...span, attributes: nextAttrs };
867
+ }
868
+ /**
869
+ * Apply the user `transformSpan` hook (if any) followed by the default
870
+ * redactor (unless disabled). Returns either the (possibly new) span to
871
+ * ship, or `null` to drop the span entirely.
872
+ *
873
+ * Ordering: user hook runs first so callers can rewrite the span freely
874
+ * (rename attrs, add new ones, scrub things the default doesn't know
875
+ * about). The default redactor then runs on whatever the user produced,
876
+ * acting as the always-on floor for documented BYOK secrets. If the user
877
+ * sets `disableDefaultRedaction: true`, the floor is skipped.
878
+ *
879
+ * Fail-closed: if the user hook throws, the span is dropped — a buggy
880
+ * hook can never accidentally ship raw, un-redacted spans.
881
+ */
882
+ redactSpan(span) {
883
+ let current = span;
884
+ if (this.transformSpanHook) {
885
+ try {
886
+ const result = this.transformSpanHook(current);
887
+ if (result === null) return null;
888
+ if (result !== void 0) current = result;
889
+ } catch (err) {
890
+ if (this.debug) {
891
+ const msg = err instanceof Error ? err.message : String(err);
892
+ console.warn(`${this.prefix} transformSpan hook threw: ${msg}`);
893
+ }
894
+ return null;
476
895
  }
477
896
  }
897
+ if (!this.disableDefaultRedaction) {
898
+ current = defaultTransformSpan(current);
899
+ }
900
+ return this.capSpanAttributes(current);
478
901
  }
479
902
  isDebugEnabled() {
480
903
  return this.debug;
@@ -482,6 +905,9 @@ var TraceShipper = class {
482
905
  authHeaders() {
483
906
  return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
484
907
  }
908
+ requestHeaders() {
909
+ return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
910
+ }
485
911
  startSpan(args) {
486
912
  var _a, _b;
487
913
  const ids = createSpanIds(args.parent);
@@ -492,8 +918,8 @@ var TraceShipper = class {
492
918
  ];
493
919
  if ((_b = args.attributes) == null ? void 0 : _b.length) attrs.push(...args.attributes);
494
920
  const span = { ids, name: args.name, startTimeUnixNano: started, attributes: attrs };
495
- if (this.localDebuggerUrl) {
496
- const openSpan = buildOtlpSpan({
921
+ this.mirrorToLocalDebugger(
922
+ buildOtlpSpan({
497
923
  ids: span.ids,
498
924
  name: span.name,
499
925
  startTimeUnixNano: span.startTimeUnixNano,
@@ -501,16 +927,21 @@ var TraceShipper = class {
501
927
  // placeholder — will be updated on endSpan
502
928
  attributes: span.attributes,
503
929
  status: { code: SpanStatusCode.UNSET }
504
- });
505
- const body = buildExportTraceServiceRequest([openSpan], this.serviceName, this.serviceVersion);
506
- mirrorTraceExportToLocalDebugger(body, {
507
- baseUrl: this.localDebuggerUrl,
508
- debug: false,
509
- sdkName: this.sdkName
510
- });
511
- }
930
+ })
931
+ );
512
932
  return span;
513
933
  }
934
+ mirrorToLocalDebugger(span) {
935
+ if (!this.localDebuggerUrl) return;
936
+ const redacted = this.redactSpan(span);
937
+ if (redacted === null) return;
938
+ const body = buildExportTraceServiceRequest([redacted], this.serviceName, this.serviceVersion);
939
+ mirrorTraceExportToLocalDebugger(body, {
940
+ baseUrl: this.localDebuggerUrl,
941
+ debug: false,
942
+ sdkName: this.sdkName
943
+ });
944
+ }
514
945
  endSpan(span, extra) {
515
946
  var _a, _b;
516
947
  if (span.endTimeUnixNano) return;
@@ -532,14 +963,7 @@ var TraceShipper = class {
532
963
  status
533
964
  });
534
965
  this.enqueue(otlp);
535
- if (this.localDebuggerUrl) {
536
- const body = buildExportTraceServiceRequest([otlp], this.serviceName, this.serviceVersion);
537
- mirrorTraceExportToLocalDebugger(body, {
538
- baseUrl: this.localDebuggerUrl,
539
- debug: false,
540
- sdkName: this.sdkName
541
- });
542
- }
966
+ this.mirrorToLocalDebugger(otlp);
543
967
  }
544
968
  createSpan(args) {
545
969
  var _a;
@@ -557,14 +981,7 @@ var TraceShipper = class {
557
981
  status: args.status
558
982
  });
559
983
  this.enqueue(otlp);
560
- if (this.localDebuggerUrl) {
561
- const body = buildExportTraceServiceRequest([otlp], this.serviceName, this.serviceVersion);
562
- mirrorTraceExportToLocalDebugger(body, {
563
- baseUrl: this.localDebuggerUrl,
564
- debug: false,
565
- sdkName: this.sdkName
566
- });
567
- }
984
+ this.mirrorToLocalDebugger(otlp);
568
985
  }
569
986
  enqueue(span) {
570
987
  if (!this.enabled) return;
@@ -576,10 +993,12 @@ var TraceShipper = class {
576
993
  )}`
577
994
  );
578
995
  }
996
+ const redacted = this.redactSpan(span);
997
+ if (redacted === null) return;
579
998
  if (this.queue.length >= this.maxQueueSize) {
580
999
  this.queue.shift();
581
1000
  }
582
- this.queue.push(span);
1001
+ this.queue.push(redacted);
583
1002
  if (this.queue.length >= this.maxBatchSize) {
584
1003
  void this.flush().catch(() => {
585
1004
  });
@@ -601,6 +1020,17 @@ var TraceShipper = class {
601
1020
  }
602
1021
  while (this.queue.length > 0) {
603
1022
  const batch = this.queue.splice(0, this.maxBatchSize);
1023
+ if (!this.writeKey) continue;
1024
+ const opts = this.requestOpts();
1025
+ if (!opts) {
1026
+ rateLimitedLog(
1027
+ `${this.prefix}.shutdown_deadline`,
1028
+ () => console.warn(
1029
+ `${this.prefix} shutdown flush deadline exceeded; dropping ${batch.length} spans`
1030
+ )
1031
+ );
1032
+ continue;
1033
+ }
604
1034
  const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
605
1035
  const url = `${this.baseUrl}traces`;
606
1036
  if (this.debug) {
@@ -609,11 +1039,7 @@ var TraceShipper = class {
609
1039
  endpoint: url
610
1040
  });
611
1041
  }
612
- const p = postJson(url, body, this.authHeaders(), {
613
- maxAttempts: 3,
614
- debug: this.debug,
615
- sdkName: this.sdkName
616
- });
1042
+ const p = postJson(url, body, this.requestHeaders(), opts);
617
1043
  this.inFlight.add(p);
618
1044
  try {
619
1045
  try {
@@ -621,21 +1047,61 @@ var TraceShipper = class {
621
1047
  if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
622
1048
  } catch (err) {
623
1049
  const msg = err instanceof Error ? err.message : String(err);
624
- console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`);
1050
+ rateLimitedLog(
1051
+ `${this.prefix}.send_spans_failed`,
1052
+ () => console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`)
1053
+ );
625
1054
  }
626
1055
  } finally {
627
1056
  this.inFlight.delete(p);
628
1057
  }
629
1058
  }
630
1059
  }
1060
+ /** See EventShipper.requestOpts — same shutdown-budget semantics. */
1061
+ requestOpts() {
1062
+ if (this.shutdownDeadlineAt !== void 0) {
1063
+ const remainingMs = this.shutdownDeadlineAt - Date.now();
1064
+ if (remainingMs <= 0) return null;
1065
+ return {
1066
+ maxAttempts: 1,
1067
+ debug: this.debug,
1068
+ sdkName: this.sdkName,
1069
+ timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
1070
+ };
1071
+ }
1072
+ if (this.hasShutdown) {
1073
+ return {
1074
+ maxAttempts: 1,
1075
+ debug: this.debug,
1076
+ sdkName: this.sdkName,
1077
+ timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
1078
+ };
1079
+ }
1080
+ return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
1081
+ }
631
1082
  async shutdown() {
632
- if (this.timer) {
633
- clearTimeout(this.timer);
634
- this.timer = void 0;
1083
+ this.hasShutdown = true;
1084
+ this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
1085
+ try {
1086
+ if (this.timer) {
1087
+ clearTimeout(this.timer);
1088
+ this.timer = void 0;
1089
+ }
1090
+ const drain = async () => {
1091
+ await this.flush();
1092
+ await Promise.all([...this.inFlight].map((p) => p.catch(() => {
1093
+ })));
1094
+ };
1095
+ const settled = await raceWithTimeout(drain(), SHUTDOWN_DEADLINE_MS);
1096
+ if (!settled) {
1097
+ rateLimitedLog(
1098
+ `${this.prefix}.shutdown_deadline`,
1099
+ () => console.warn(`${this.prefix} shutdown flush deadline exceeded; abandoning in-flight spans`)
1100
+ );
1101
+ }
1102
+ } finally {
1103
+ this.shutdownDeadlineAt = void 0;
635
1104
  }
636
- await this.flush();
637
- await Promise.all([...this.inFlight].map((p) => p.catch(() => {
638
- })));
639
1105
  }
640
1106
  };
641
1107
 
@@ -688,7 +1154,7 @@ import { homedir, userInfo } from "os";
688
1154
  import { dirname, join } from "path";
689
1155
  var CONFIG_PATH = join(homedir(), ".config", "raindrop", "config.json");
690
1156
  function loadConfig() {
691
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
1157
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
692
1158
  let file = {};
693
1159
  try {
694
1160
  if (existsSync(CONFIG_PATH)) {
@@ -728,9 +1194,10 @@ function loadConfig() {
728
1194
  return {
729
1195
  writeKey: (_c = (_b = process.env["RAINDROP_WRITE_KEY"]) != null ? _b : file.write_key) != null ? _c : "",
730
1196
  endpoint: (_e = (_d = process.env["RAINDROP_API_URL"]) != null ? _d : file.api_url) != null ? _e : "https://api.raindrop.ai/v1",
731
- userId: (_g = (_f = process.env["RAINDROP_USER_ID"]) != null ? _f : file.user_id) != null ? _g : systemUser,
732
- convoId: ((_h = process.env["RAINDROP_CONVO_ID"]) == null ? void 0 : _h.trim()) || void 0,
733
- debug: process.env["RAINDROP_DEBUG"] === "true" ? true : (_i = file.debug) != null ? _i : false,
1197
+ projectId: (_f = process.env["RAINDROP_PROJECT_ID"]) != null ? _f : file.project_id,
1198
+ userId: (_h = (_g = process.env["RAINDROP_USER_ID"]) != null ? _g : file.user_id) != null ? _h : systemUser,
1199
+ convoId: ((_i = process.env["RAINDROP_CONVO_ID"]) == null ? void 0 : _i.trim()) || void 0,
1200
+ debug: process.env["RAINDROP_DEBUG"] === "true" ? true : (_j = file.debug) != null ? _j : false,
734
1201
  enabled: (() => {
735
1202
  var _a2;
736
1203
  const env = process.env["RAINDROP_ENABLED"];
@@ -738,7 +1205,7 @@ function loadConfig() {
738
1205
  const lower = env.toLowerCase();
739
1206
  return lower !== "false" && env !== "0";
740
1207
  })(),
741
- eventName: (_k = (_j = process.env["RAINDROP_EVENT_NAME"]) != null ? _j : file.event_name) != null ? _k : "claude_code_session",
1208
+ eventName: (_l = (_k = process.env["RAINDROP_EVENT_NAME"]) != null ? _k : file.event_name) != null ? _l : "claude_code_session",
742
1209
  customProperties,
743
1210
  selfDiagnostics
744
1211
  };
@@ -984,18 +1451,85 @@ function transcriptToProperties(summary) {
984
1451
 
985
1452
  // src/event-mapper.ts
986
1453
  var MAX_ATTR_LENGTH = 32768;
1454
+ var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
1455
+ var MAX_TEXT_FIELD_CHARS = 1e6;
1456
+ var MAX_BOUNDED_DEPTH = 12;
987
1457
  function truncate(value) {
988
1458
  if (value === void 0) return void 0;
989
1459
  if (value.length <= MAX_ATTR_LENGTH) return value;
990
1460
  const suffix = "\n...[truncated]";
991
1461
  return value.slice(0, MAX_ATTR_LENGTH - suffix.length) + suffix;
992
1462
  }
1463
+ function capText2(value, limit = MAX_TEXT_FIELD_CHARS) {
1464
+ if (value.length <= limit) return value;
1465
+ if (limit > TRUNCATION_MARKER2.length) {
1466
+ return value.slice(0, limit - TRUNCATION_MARKER2.length) + TRUNCATION_MARKER2;
1467
+ }
1468
+ return value.slice(0, limit);
1469
+ }
1470
+ function boundedClone2(value, budget, depth) {
1471
+ if (budget.remaining <= 0) return TRUNCATION_MARKER2;
1472
+ if (typeof value === "string") {
1473
+ if (value.length > budget.remaining) {
1474
+ const taken = value.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
1475
+ budget.remaining = 0;
1476
+ return taken;
1477
+ }
1478
+ budget.remaining -= Math.max(value.length, 1);
1479
+ return value;
1480
+ }
1481
+ if (value === null || typeof value === "number" || typeof value === "boolean") {
1482
+ budget.remaining -= 8;
1483
+ return value;
1484
+ }
1485
+ if (typeof value !== "object") {
1486
+ budget.remaining -= 8;
1487
+ return value;
1488
+ }
1489
+ if (depth >= MAX_BOUNDED_DEPTH) {
1490
+ budget.remaining -= 16;
1491
+ return `<max depth: ${TRUNCATION_MARKER2}>`;
1492
+ }
1493
+ const withToJson = value;
1494
+ if (typeof withToJson.toJSON === "function") {
1495
+ try {
1496
+ return boundedClone2(withToJson.toJSON(), budget, depth + 1);
1497
+ } catch (e) {
1498
+ }
1499
+ }
1500
+ if (Array.isArray(value)) {
1501
+ const out2 = [];
1502
+ for (const item of value) {
1503
+ if (budget.remaining <= 0) {
1504
+ out2.push(TRUNCATION_MARKER2);
1505
+ break;
1506
+ }
1507
+ out2.push(boundedClone2(item, budget, depth + 1));
1508
+ }
1509
+ return out2;
1510
+ }
1511
+ const out = {};
1512
+ for (const key of Object.keys(value)) {
1513
+ if (budget.remaining <= 0) {
1514
+ out["..."] = TRUNCATION_MARKER2;
1515
+ break;
1516
+ }
1517
+ budget.remaining -= Math.max(key.length, 1);
1518
+ out[key] = boundedClone2(value[key], budget, depth + 1);
1519
+ }
1520
+ return out;
1521
+ }
993
1522
  function safeStringify(value) {
994
1523
  if (value === void 0 || value === null) return void 0;
995
1524
  try {
996
- return truncate(JSON.stringify(value));
1525
+ const pruned = boundedClone2(
1526
+ value,
1527
+ { remaining: MAX_ATTR_LENGTH + TRUNCATION_MARKER2.length + 256 },
1528
+ 0
1529
+ );
1530
+ return truncate(JSON.stringify(pruned));
997
1531
  } catch (e) {
998
- return truncate(String(value));
1532
+ return truncate(capText2(String(value), MAX_ATTR_LENGTH + 16));
999
1533
  }
1000
1534
  }
1001
1535
  var STATE_DIR = join2(tmpdir(), "raindrop-claude-code");
@@ -1176,7 +1710,9 @@ async function handleUserPromptSubmit(payload, convoId, config, properties, even
1176
1710
  userId: config.userId,
1177
1711
  convoId,
1178
1712
  eventName: config.eventName,
1179
- input: payload.prompt,
1713
+ // Capped so a pasted multi-MB prompt can't produce an event that the
1714
+ // ingest API rejects (silent data loss) — it lands truncated instead.
1715
+ input: payload.prompt !== void 0 ? capText2(payload.prompt) : void 0,
1180
1716
  model,
1181
1717
  properties
1182
1718
  };
@@ -1299,7 +1835,7 @@ async function handlePostCompact(payload, eventId, convoId, config, properties,
1299
1835
  properties: {
1300
1836
  ...properties,
1301
1837
  compaction_trigger: payload.trigger,
1302
- compact_summary: payload.compact_summary
1838
+ compact_summary: payload.compact_summary !== void 0 ? capText2(payload.compact_summary) : void 0
1303
1839
  }
1304
1840
  });
1305
1841
  }
@@ -1534,7 +2070,7 @@ function enrichFromTranscript(payload, eventId, traceShipper) {
1534
2070
  attrInt("gen_ai.usage.prompt_tokens", phase.inputTokens),
1535
2071
  attrInt("gen_ai.usage.completion_tokens", phase.outputTokens),
1536
2072
  ...phase.cacheReadTokens > 0 ? [attrInt("gen_ai.usage.cache_read_tokens", phase.cacheReadTokens)] : [],
1537
- attrString("ai.response.text", outputText),
2073
+ attrString("ai.response.text", truncate(outputText)),
1538
2074
  ...toolCallNames ? [attrString("ai.toolCall.names", toolCallNames)] : [],
1539
2075
  ...phase.hasThinking ? [attrString("ai.has_thinking", "true")] : [],
1540
2076
  attrInt("ai.llm_call.index", emittedPhaseIndex)
@@ -1594,7 +2130,7 @@ async function handleStopOrFailure(payload, eventId, convoId, config, properties
1594
2130
  userId: config.userId,
1595
2131
  convoId,
1596
2132
  eventName: config.eventName,
1597
- output,
2133
+ output: output !== void 0 ? capText2(output) : void 0,
1598
2134
  ...(summary == null ? void 0 : summary.model) ? { model: summary.model } : {},
1599
2135
  properties: {
1600
2136
  ...properties,
@@ -1625,6 +2161,7 @@ import { join as join3 } from "path";
1625
2161
  var DEFAULT_PORT = 5899;
1626
2162
  var DEFAULT_URL = `http://localhost:${DEFAULT_PORT}/v1/`;
1627
2163
  var HEALTH_TIMEOUT_MS = 300;
2164
+ var MIRROR_TIMEOUT_MS = 2e3;
1628
2165
  var CACHE_DIR = join3(tmpdir2(), "raindrop-claude-code");
1629
2166
  var CACHE_FILE = join3(CACHE_DIR, "debugger_cache");
1630
2167
  var CACHE_TTL_MS = 5e3;
@@ -1648,10 +2185,8 @@ function writeCache(url) {
1648
2185
  async function healthPing(baseUrl) {
1649
2186
  const healthUrl = baseUrl.replace(/\/v1\/$/, "/health");
1650
2187
  try {
1651
- const controller = new AbortController();
1652
- const timeout = setTimeout(() => controller.abort(), HEALTH_TIMEOUT_MS);
1653
- const resp = await fetch(healthUrl, { signal: controller.signal });
1654
- clearTimeout(timeout);
2188
+ const signal = AbortSignal.timeout(HEALTH_TIMEOUT_MS);
2189
+ const resp = await fetch(healthUrl, { signal });
1655
2190
  if (resp.ok) {
1656
2191
  const body = await resp.json();
1657
2192
  if (body.ok) return baseUrl;
@@ -1694,7 +2229,8 @@ function mirrorEventToLocalDebugger(baseUrl, payload, debug) {
1694
2229
  void fetch(url, {
1695
2230
  method: "POST",
1696
2231
  headers: { "Content-Type": "application/json" },
1697
- body: JSON.stringify(payload)
2232
+ body: JSON.stringify(payload),
2233
+ signal: AbortSignal.timeout(MIRROR_TIMEOUT_MS)
1698
2234
  }).catch(() => {
1699
2235
  });
1700
2236
  }
@@ -1835,6 +2371,7 @@ async function executeTool(args) {
1835
2371
  writeKey: config.writeKey,
1836
2372
  endpoint: config.endpoint,
1837
2373
  debug: false,
2374
+ projectId: config.projectId,
1838
2375
  enabled: true
1839
2376
  });
1840
2377
  try {