raindrop-ai 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,9 +1,20 @@
1
1
  import {
2
+ DEFAULT_REQUEST_TIMEOUT_MS,
3
+ MAX_RETRY_DELAY_MS,
4
+ POST_SHUTDOWN_TIMEOUT_MS,
5
+ SHUTDOWN_DEADLINE_MS,
6
+ capText,
2
7
  mirrorPartialEventToLocalDebugger,
3
8
  package_default,
9
+ raceWithTimeout,
10
+ rateLimitedLog,
11
+ redactUrlForLog,
4
12
  resolveLocalDebuggerBaseUrl,
13
+ resolveMaxTextFieldChars,
14
+ setDefaultMaxTextFieldChars,
15
+ stringifyBounded,
5
16
  tracing
6
- } from "./chunk-DP5ED5DR.mjs";
17
+ } from "./chunk-UICWZEYM.mjs";
7
18
  import {
8
19
  __commonJS,
9
20
  __toESM
@@ -9957,7 +9968,6 @@ var PartialClientAiTrack = ClientAiTrack.extend({
9957
9968
  var AttachmentTypeSchema = z.enum(["code", "text", "image", "iframe"]);
9958
9969
 
9959
9970
  // src/index.ts
9960
- process.env.TRACELOOP_TELEMETRY = "false";
9961
9971
  var MAX_INGEST_SIZE_BYTES = 1 * 1024 * 1024;
9962
9972
  var SELF_DIAGNOSTICS_TOOL_NAME_DEFAULT = "__raindrop_report";
9963
9973
  var SELF_DIAGNOSTICS_SOURCE_DEFAULT = "agent_reporting_tool";
@@ -10082,12 +10092,12 @@ function parseRetryAfter(headers) {
10082
10092
  }
10083
10093
  function getRetryDelayMs(attemptNumber, previousError) {
10084
10094
  if (previousError && typeof previousError.retryAfterMs === "number") {
10085
- return Math.max(0, previousError.retryAfterMs);
10095
+ return Math.min(Math.max(0, previousError.retryAfterMs), MAX_RETRY_DELAY_MS);
10086
10096
  }
10087
10097
  if (attemptNumber <= 1) return 0;
10088
10098
  const base = 500;
10089
10099
  const factor = Math.pow(2, attemptNumber - 2);
10090
- return base * factor;
10100
+ return Math.min(base * factor, MAX_RETRY_DELAY_MS);
10091
10101
  }
10092
10102
  async function withRetry(operation, opName, maxAttempts, debugLogs = false) {
10093
10103
  var _a;
@@ -10126,7 +10136,16 @@ var Raindrop = class {
10126
10136
  this.partialEventBuffer = /* @__PURE__ */ new Map();
10127
10137
  this.partialEventTimeouts = /* @__PURE__ */ new Map();
10128
10138
  this.inFlightRequests = /* @__PURE__ */ new Set();
10139
+ /**
10140
+ * Set once `close()` begins and never cleared. Sends issued after the
10141
+ * drain window (stragglers, or flush work the deadline abandoned
10142
+ * mid-drain) run as a single short attempt instead of regaining the full
10143
+ * retry schedule.
10144
+ */
10145
+ this.hasShutdown = false;
10129
10146
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
10147
+ setDefaultMaxTextFieldChars(config.maxTextFieldChars);
10148
+ this.maxTextFieldChars = resolveMaxTextFieldChars(config.maxTextFieldChars);
10130
10149
  this.writeKey = (_a = config.writeKey) != null ? _a : "";
10131
10150
  this.localDebuggerUrlOpt = config.localWorkshopUrl === false ? null : config.localWorkshopUrl;
10132
10151
  this.apiUrl = (_b = this.formatEndpoint(config.endpoint)) != null ? _b : `https://api.raindrop.ai/v1/`;
@@ -10349,14 +10368,16 @@ var Raindrop = class {
10349
10368
  eventId,
10350
10369
  userId,
10351
10370
  model,
10352
- input,
10353
- output,
10371
+ input: rawInput,
10372
+ output: rawOutput,
10354
10373
  convoId,
10355
10374
  attachments,
10356
10375
  properties,
10357
10376
  timestamp,
10358
10377
  ...rest
10359
10378
  } = e;
10379
+ const input = capText(rawInput, this.maxTextFieldChars);
10380
+ const output = capText(rawOutput, this.maxTextFieldChars);
10360
10381
  const customEventId = eventId != null ? eventId : crypto.randomUUID();
10361
10382
  const parsedAttachments = AttachmentSchema.array().safeParse(attachments != null ? attachments : []);
10362
10383
  if (!parsedAttachments.success) {
@@ -10563,20 +10584,51 @@ var Raindrop = class {
10563
10584
  void fetch(`${localUrl}${endpoint}`, {
10564
10585
  method: "POST",
10565
10586
  headers,
10566
- body: JSON.stringify(body)
10587
+ body: JSON.stringify(body),
10588
+ // Bounded like the cloud paths: a black-holed Workshop URL must not
10589
+ // pin connections indefinitely (fire-and-forget still holds sockets).
10590
+ signal: AbortSignal.timeout(DEFAULT_REQUEST_TIMEOUT_MS)
10567
10591
  }).catch(() => {
10568
10592
  });
10569
10593
  } catch (e) {
10570
10594
  }
10571
10595
  }
10596
+ /**
10597
+ * Attempts/timeout budget for one POST, honoring the close() deadline.
10598
+ * Returns `null` when the shutdown budget is exhausted — the caller must
10599
+ * drop the payload instead of issuing a request that could outlive
10600
+ * process exit. Checked fresh on every send so a deadline that expires
10601
+ * mid-drain takes effect immediately.
10602
+ */
10603
+ requestBudget() {
10604
+ if (this.shutdownDeadlineAt !== void 0) {
10605
+ const remainingMs = this.shutdownDeadlineAt - Date.now();
10606
+ if (remainingMs <= 0) return null;
10607
+ return { maxAttempts: 1, timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs) };
10608
+ }
10609
+ if (this.hasShutdown) {
10610
+ return { maxAttempts: 1, timeoutMs: POST_SHUTDOWN_TIMEOUT_MS };
10611
+ }
10612
+ return { maxAttempts: 3, timeoutMs: DEFAULT_REQUEST_TIMEOUT_MS };
10613
+ }
10614
+ warnShutdownDrop(what) {
10615
+ rateLimitedLog(
10616
+ "js-sdk.shutdown_deadline",
10617
+ () => console.warn(`[raindrop] shutdown flush deadline exceeded; dropping ${what}`)
10618
+ );
10619
+ }
10572
10620
  async sendBatchToApi(events, endpoint) {
10573
- var _a;
10574
10621
  const body = events.map(({ type, ...event }) => event);
10575
10622
  this.mirrorBatchToLocalDebugger(endpoint, body);
10576
10623
  if (this.localOnly) {
10577
10624
  return;
10578
10625
  }
10579
- const opName = `POST ${endpoint}`;
10626
+ const opName = `POST ${redactUrlForLog(this.apiUrl + endpoint)}`;
10627
+ const budget = this.requestBudget();
10628
+ if (!budget) {
10629
+ this.warnShutdownDrop(`${events.length} event(s) for ${endpoint}`);
10630
+ return;
10631
+ }
10580
10632
  try {
10581
10633
  const response = await withRetry(
10582
10634
  async () => {
@@ -10586,7 +10638,11 @@ var Raindrop = class {
10586
10638
  "Content-Type": "application/json",
10587
10639
  Authorization: `Bearer ${this.writeKey}`
10588
10640
  },
10589
- body: JSON.stringify(body)
10641
+ body: JSON.stringify(body),
10642
+ // Bounded: a dead or black-holed endpoint must never pin this
10643
+ // request (and the flush/close paths awaiting it) until the OS
10644
+ // gives up.
10645
+ signal: AbortSignal.timeout(budget.timeoutMs)
10590
10646
  });
10591
10647
  if (!resp.ok) {
10592
10648
  const text = await resp.text().catch(() => "");
@@ -10600,7 +10656,7 @@ var Raindrop = class {
10600
10656
  return resp;
10601
10657
  },
10602
10658
  opName,
10603
- 3,
10659
+ budget.maxAttempts,
10604
10660
  this.debugLogs
10605
10661
  );
10606
10662
  if (this.debugLogs) {
@@ -10609,9 +10665,18 @@ var Raindrop = class {
10609
10665
  return response;
10610
10666
  } catch (error) {
10611
10667
  if (error == null ? void 0 : error.response) {
10612
- console.error(`[raindrop] Error in response (${opName}): ` + error.response.data);
10668
+ rateLimitedLog(
10669
+ `js-sdk.batch_failed.${endpoint}`,
10670
+ () => console.error(`[raindrop] Error in response (${opName}): ` + error.response.data)
10671
+ );
10613
10672
  } else {
10614
- console.error(`[raindrop] Error (${opName}): ` + ((_a = error == null ? void 0 : error.message) != null ? _a : String(error)));
10673
+ rateLimitedLog(
10674
+ `js-sdk.batch_failed.${endpoint}`,
10675
+ () => {
10676
+ var _a;
10677
+ return console.error(`[raindrop] Error (${opName}): ` + ((_a = error == null ? void 0 : error.message) != null ? _a : String(error)));
10678
+ }
10679
+ );
10615
10680
  }
10616
10681
  }
10617
10682
  }
@@ -10668,6 +10733,12 @@ var Raindrop = class {
10668
10733
  console.warn("[raindrop] trackAiPartial requires an eventId.");
10669
10734
  return Promise.resolve();
10670
10735
  }
10736
+ if (typeof rest.input === "string") {
10737
+ rest.input = capText(rest.input, this.maxTextFieldChars);
10738
+ }
10739
+ if (typeof rest.output === "string") {
10740
+ rest.output = capText(rest.output, this.maxTextFieldChars);
10741
+ }
10671
10742
  const existingEvent = this.partialEventBuffer.get(eventId) || {};
10672
10743
  const mergedEvent = this.deepMergeObjects(
10673
10744
  { ...existingEvent },
@@ -10698,7 +10769,7 @@ var Raindrop = class {
10698
10769
  this.partialEventTimeouts.set(eventId, newTimeout);
10699
10770
  if (this.debugLogs) {
10700
10771
  console.log(
10701
- `[raindrop] Updated partial event buffer for eventId: ${eventId}. Timeout reset (20s). Event: ${JSON.stringify(mergedEvent)}`
10772
+ `[raindrop] Updated partial event buffer for eventId: ${eventId}. Timeout reset (2s). Event: ${stringifyBounded(mergedEvent, 2e3)}`
10702
10773
  );
10703
10774
  }
10704
10775
  return Promise.resolve();
@@ -10714,9 +10785,12 @@ var Raindrop = class {
10714
10785
  try {
10715
10786
  await flushPromise;
10716
10787
  } catch (error) {
10717
- console.error(
10718
- `[raindrop] Failed to flush partial event ${eventId}:`,
10719
- error instanceof Error ? error : String(error)
10788
+ rateLimitedLog(
10789
+ "js-sdk.partial_flush_failed",
10790
+ () => console.error(
10791
+ `[raindrop] Failed to flush partial event ${eventId}:`,
10792
+ error instanceof Error ? error : String(error)
10793
+ )
10720
10794
  );
10721
10795
  } finally {
10722
10796
  this.inFlightRequests.delete(flushPromise);
@@ -10784,7 +10858,7 @@ var Raindrop = class {
10784
10858
  const parsed = PartialClientAiTrack.partial().safeParse(eventToSend);
10785
10859
  if (!parsed.success) {
10786
10860
  console.warn(
10787
- `[raindrop] Invalid accumulated data for partial eventId ${eventId}. Event not sent. Errors: ${this.formatZodError(parsed.error)} Data: ${JSON.stringify(eventToSend)}`
10861
+ `[raindrop] Invalid accumulated data for partial eventId ${eventId}. Event not sent. Errors: ${this.formatZodError(parsed.error)} Data: ${stringifyBounded(eventToSend, 2e3)}`
10788
10862
  );
10789
10863
  return;
10790
10864
  }
@@ -10808,7 +10882,6 @@ var Raindrop = class {
10808
10882
  * @param event - The event data conforming to ClientAiTrack schema.
10809
10883
  */
10810
10884
  async sendPartialEvent(event) {
10811
- var _a;
10812
10885
  const endpoint = "events/track_partial";
10813
10886
  const opName = `POST ${endpoint}`;
10814
10887
  mirrorPartialEventToLocalDebugger(event, {
@@ -10820,6 +10893,11 @@ var Raindrop = class {
10820
10893
  if (this.localOnly) {
10821
10894
  return;
10822
10895
  }
10896
+ const budget = this.requestBudget();
10897
+ if (!budget) {
10898
+ this.warnShutdownDrop(`partial event ${event.event_id}`);
10899
+ return;
10900
+ }
10823
10901
  try {
10824
10902
  const response = await withRetry(
10825
10903
  async () => {
@@ -10829,8 +10907,10 @@ var Raindrop = class {
10829
10907
  "Content-Type": "application/json",
10830
10908
  Authorization: `Bearer ${this.writeKey}`
10831
10909
  },
10832
- body: JSON.stringify(event)
10910
+ body: JSON.stringify(event),
10833
10911
  // Send the single event object
10912
+ // Bounded: see sendBatchToApi.
10913
+ signal: AbortSignal.timeout(budget.timeoutMs)
10834
10914
  });
10835
10915
  if (!resp.ok) {
10836
10916
  const errorBody = await resp.text().catch(() => "");
@@ -10844,7 +10924,7 @@ var Raindrop = class {
10844
10924
  return resp;
10845
10925
  },
10846
10926
  opName,
10847
- 3,
10927
+ budget.maxAttempts,
10848
10928
  this.debugLogs
10849
10929
  );
10850
10930
  if (this.debugLogs) {
@@ -10854,8 +10934,14 @@ var Raindrop = class {
10854
10934
  }
10855
10935
  return response;
10856
10936
  } catch (error) {
10857
- console.error(
10858
- `[raindrop] Failed to send partial event ${event.event_id} to ${endpoint}: ${(_a = error == null ? void 0 : error.message) != null ? _a : String(error)}`
10937
+ rateLimitedLog(
10938
+ "js-sdk.partial_failed",
10939
+ () => {
10940
+ var _a;
10941
+ return console.error(
10942
+ `[raindrop] Failed to send partial event ${event.event_id} to ${endpoint}: ${(_a = error == null ? void 0 : error.message) != null ? _a : String(error)}`
10943
+ );
10944
+ }
10859
10945
  );
10860
10946
  throw error;
10861
10947
  }
@@ -10874,7 +10960,21 @@ var Raindrop = class {
10874
10960
  }
10875
10961
  }
10876
10962
  async close() {
10877
- await this._tracing.close();
10963
+ this.hasShutdown = true;
10964
+ this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
10965
+ try {
10966
+ await this.closeWithinDeadline();
10967
+ } finally {
10968
+ this.shutdownDeadlineAt = void 0;
10969
+ }
10970
+ }
10971
+ async closeWithinDeadline() {
10972
+ const remainingMs = () => {
10973
+ var _a;
10974
+ return Math.max(0, ((_a = this.shutdownDeadlineAt) != null ? _a : Date.now()) - Date.now());
10975
+ };
10976
+ const tracingSettled = await raceWithTimeout(this._tracing.close(), remainingMs());
10977
+ if (!tracingSettled) this.warnShutdownDrop("in-flight trace flush");
10878
10978
  try {
10879
10979
  await this.flush();
10880
10980
  } catch (error) {
@@ -10903,7 +11003,12 @@ var Raindrop = class {
10903
11003
  );
10904
11004
  }
10905
11005
  try {
10906
- await Promise.all(Array.from(this.inFlightRequests));
11006
+ const settled = await raceWithTimeout(
11007
+ Promise.allSettled(Array.from(this.inFlightRequests)).then(() => {
11008
+ }),
11009
+ remainingMs()
11010
+ );
11011
+ if (!settled) this.warnShutdownDrop("in-flight partial event request(s)");
10907
11012
  } catch (e) {
10908
11013
  console.error(`[raindrop] Error waiting for in-flight requests:`, e);
10909
11014
  }