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.js CHANGED
@@ -5771,7 +5771,9 @@ __export(index_exports, {
5771
5771
  });
5772
5772
  module.exports = __toCommonJS(index_exports);
5773
5773
 
5774
- // ../core/dist/chunk-OLYXZCOK.js
5774
+ // ../core/dist/chunk-U5HUTMR5.js
5775
+ var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
5776
+ var MAX_RETRY_DELAY_MS = 3e4;
5775
5777
  function wait(ms) {
5776
5778
  return new Promise((resolve) => setTimeout(resolve, ms));
5777
5779
  }
@@ -5779,6 +5781,46 @@ function formatEndpoint(endpoint) {
5779
5781
  if (!endpoint) return void 0;
5780
5782
  return endpoint.endsWith("/") ? endpoint : `${endpoint}/`;
5781
5783
  }
5784
+ function redactUrlForLog(url) {
5785
+ try {
5786
+ const parsed = new URL(url);
5787
+ parsed.username = "";
5788
+ parsed.password = "";
5789
+ parsed.search = "";
5790
+ parsed.hash = "";
5791
+ return parsed.toString();
5792
+ } catch (e) {
5793
+ return "<unparseable-url>";
5794
+ }
5795
+ }
5796
+ var RATE_LIMITED_LOG_INTERVAL_MS = 3e4;
5797
+ var rateLimitedLogLast = /* @__PURE__ */ new Map();
5798
+ function rateLimitedLog(key, log) {
5799
+ const now = Date.now();
5800
+ const last = rateLimitedLogLast.get(key);
5801
+ if (last !== void 0 && now - last < RATE_LIMITED_LOG_INTERVAL_MS) {
5802
+ return false;
5803
+ }
5804
+ rateLimitedLogLast.set(key, now);
5805
+ log();
5806
+ return true;
5807
+ }
5808
+ async function raceWithTimeout(promise, timeoutMs) {
5809
+ let timer;
5810
+ const settledInTime = await Promise.race([
5811
+ promise.then(
5812
+ () => true,
5813
+ () => true
5814
+ ),
5815
+ new Promise((resolve) => {
5816
+ var _a;
5817
+ timer = setTimeout(() => resolve(false), Math.max(0, timeoutMs));
5818
+ (_a = timer.unref) == null ? void 0 : _a.call(timer);
5819
+ })
5820
+ ]);
5821
+ if (timer) clearTimeout(timer);
5822
+ return settledInTime;
5823
+ }
5782
5824
  function parseRetryAfter(headers) {
5783
5825
  var _a;
5784
5826
  const value = (_a = headers.get("Retry-After")) != null ? _a : headers.get("retry-after");
@@ -5795,12 +5837,12 @@ function parseRetryAfter(headers) {
5795
5837
  function getRetryDelayMs(attemptNumber, previousError) {
5796
5838
  if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
5797
5839
  const v = previousError.retryAfterMs;
5798
- if (typeof v === "number") return Math.max(0, v);
5840
+ if (typeof v === "number") return Math.min(Math.max(0, v), MAX_RETRY_DELAY_MS);
5799
5841
  }
5800
5842
  if (attemptNumber <= 1) return 0;
5801
5843
  const base = 500;
5802
5844
  const factor = Math.pow(2, attemptNumber - 2);
5803
- return base * factor;
5845
+ return Math.min(base * factor, MAX_RETRY_DELAY_MS);
5804
5846
  }
5805
5847
  async function withRetry(operation, opName, opts) {
5806
5848
  const prefix = opts.sdkName ? `[raindrop-ai/${opts.sdkName}]` : "[raindrop-ai/core]";
@@ -5835,7 +5877,9 @@ async function withRetry(operation, opName, opts) {
5835
5877
  throw lastError instanceof Error ? lastError : new Error(String(lastError));
5836
5878
  }
5837
5879
  async function postJson(url, body, headers, opts) {
5838
- const opName = `POST ${url}`;
5880
+ var _a;
5881
+ const opName = `POST ${redactUrlForLog(url)}`;
5882
+ const timeoutMs = (_a = opts.timeoutMs) != null ? _a : DEFAULT_REQUEST_TIMEOUT_MS;
5839
5883
  await withRetry(
5840
5884
  async () => {
5841
5885
  const resp = await fetch(url, {
@@ -5844,7 +5888,8 @@ async function postJson(url, body, headers, opts) {
5844
5888
  "Content-Type": "application/json",
5845
5889
  ...headers
5846
5890
  },
5847
- body: JSON.stringify(body)
5891
+ body: JSON.stringify(body),
5892
+ signal: AbortSignal.timeout(timeoutMs)
5848
5893
  });
5849
5894
  if (!resp.ok) {
5850
5895
  const text = await resp.text().catch(() => "");
@@ -5861,6 +5906,108 @@ async function postJson(url, body, headers, opts) {
5861
5906
  opts
5862
5907
  );
5863
5908
  }
5909
+ var DEFAULT_MAX_TEXT_FIELD_CHARS = 1e6;
5910
+ var TRUNCATION_MARKER = "...[truncated by raindrop]";
5911
+ var BOUNDED_CLONE_MAX_DEPTH = 12;
5912
+ var BOUNDED_CLONE_SLACK = 256;
5913
+ var currentDefaultMaxTextFieldChars = DEFAULT_MAX_TEXT_FIELD_CHARS;
5914
+ function setDefaultMaxTextFieldChars(value) {
5915
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
5916
+ currentDefaultMaxTextFieldChars = Math.floor(value);
5917
+ }
5918
+ }
5919
+ function resolveMaxTextFieldChars(value) {
5920
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
5921
+ return Math.floor(value);
5922
+ }
5923
+ return currentDefaultMaxTextFieldChars;
5924
+ }
5925
+ function truncateToLimit(text, limit) {
5926
+ if (limit > TRUNCATION_MARKER.length) {
5927
+ return text.slice(0, limit - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;
5928
+ }
5929
+ return text.slice(0, Math.max(0, limit));
5930
+ }
5931
+ function capText(value, limit) {
5932
+ if (typeof value !== "string") return value;
5933
+ const max = limit != null ? limit : currentDefaultMaxTextFieldChars;
5934
+ if (value.length <= max) return value;
5935
+ return truncateToLimit(value, max);
5936
+ }
5937
+ function boundedClone(obj, charBudget) {
5938
+ let budget = charBudget;
5939
+ const seen = /* @__PURE__ */ new WeakSet();
5940
+ const walk = (node, depth) => {
5941
+ if (budget <= 0) return TRUNCATION_MARKER;
5942
+ if (typeof node === "string") {
5943
+ if (node.length > budget) {
5944
+ const taken = node.slice(0, Math.max(0, budget)) + TRUNCATION_MARKER;
5945
+ budget = 0;
5946
+ return taken;
5947
+ }
5948
+ budget -= Math.max(node.length, 1);
5949
+ return node;
5950
+ }
5951
+ if (node === null || typeof node === "number" || typeof node === "boolean") {
5952
+ budget -= 8;
5953
+ return node;
5954
+ }
5955
+ if (typeof node !== "object") {
5956
+ budget -= 8;
5957
+ return node;
5958
+ }
5959
+ if (seen.has(node)) return "[CIRCULAR]";
5960
+ if (depth >= BOUNDED_CLONE_MAX_DEPTH) {
5961
+ budget -= 16;
5962
+ return `<max depth>`;
5963
+ }
5964
+ seen.add(node);
5965
+ if (Array.isArray(node)) {
5966
+ const out2 = [];
5967
+ for (const v of node) {
5968
+ if (budget <= 0) {
5969
+ out2.push(TRUNCATION_MARKER);
5970
+ break;
5971
+ }
5972
+ out2.push(walk(v, depth + 1));
5973
+ }
5974
+ return out2;
5975
+ }
5976
+ if (typeof node.toJSON === "function") {
5977
+ budget -= 16;
5978
+ return node;
5979
+ }
5980
+ const out = {};
5981
+ for (const k in node) {
5982
+ if (!Object.prototype.hasOwnProperty.call(node, k)) continue;
5983
+ if (budget <= 0) {
5984
+ out["..."] = TRUNCATION_MARKER;
5985
+ break;
5986
+ }
5987
+ const key = walk(k, depth + 1);
5988
+ out[key] = walk(node[k], depth + 1);
5989
+ }
5990
+ return out;
5991
+ };
5992
+ return walk(obj, 0);
5993
+ }
5994
+ function stringifyBounded(value, limit) {
5995
+ const max = limit != null ? limit : currentDefaultMaxTextFieldChars;
5996
+ if (typeof value === "string") {
5997
+ return capText(value, max);
5998
+ }
5999
+ const pruned = boundedClone(value, max + TRUNCATION_MARKER.length + BOUNDED_CLONE_SLACK);
6000
+ let json;
6001
+ try {
6002
+ json = JSON.stringify(pruned);
6003
+ } catch (e) {
6004
+ json = void 0;
6005
+ }
6006
+ if (json === void 0) {
6007
+ return capText(String(value), max);
6008
+ }
6009
+ return json.length <= max ? json : truncateToLimit(json, max);
6010
+ }
5864
6011
  var LOCAL_DEBUGGER_ENV_VAR = "RAINDROP_LOCAL_DEBUGGER";
5865
6012
  var WORKSHOP_ENV_VAR = "RAINDROP_WORKSHOP";
5866
6013
  var DEFAULT_LOCAL_WORKSHOP_URL = "http://localhost:5899/v1/";
@@ -5969,6 +6116,13 @@ function sendLocalDebuggerLiveEvent(event, options = {}) {
5969
6116
  ).catch(() => {
5970
6117
  });
5971
6118
  }
6119
+ var SHUTDOWN_DEADLINE_MS = 1e4;
6120
+ var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
6121
+ var DEFAULT_REDACT_ATTRIBUTE_KEYS = [
6122
+ "ai.request.providerOptions",
6123
+ "ai.response.providerMetadata"
6124
+ ];
6125
+ var REDACT_JSON_ATTRIBUTE_KEYS = new Set(DEFAULT_REDACT_ATTRIBUTE_KEYS);
5972
6126
 
5973
6127
  // ../core/dist/index.node.js
5974
6128
  var import_async_hooks = require("async_hooks");
@@ -10082,7 +10236,7 @@ var SignalEventSchema = external_exports.object({
10082
10236
  // package.json
10083
10237
  var package_default = {
10084
10238
  name: "raindrop-ai",
10085
- version: "0.1.1",
10239
+ version: "0.1.2",
10086
10240
  main: "dist/index.js",
10087
10241
  module: "dist/index.mjs",
10088
10242
  types: "dist/index.d.ts",
@@ -10326,12 +10480,12 @@ function parseRetryAfter2(headers) {
10326
10480
  function getRetryDelayMs2(attemptNumber, previousError) {
10327
10481
  if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
10328
10482
  const v = previousError.retryAfterMs;
10329
- if (typeof v === "number") return Math.max(0, v);
10483
+ if (typeof v === "number") return Math.min(Math.max(0, v), MAX_RETRY_DELAY_MS);
10330
10484
  }
10331
10485
  if (attemptNumber <= 1) return 0;
10332
10486
  const base = 500;
10333
10487
  const factor = Math.pow(2, attemptNumber - 2);
10334
- return base * factor;
10488
+ return Math.min(base * factor, MAX_RETRY_DELAY_MS);
10335
10489
  }
10336
10490
  async function withRetry2(operation, opName, opts) {
10337
10491
  let lastError = void 0;
@@ -10363,7 +10517,9 @@ async function withRetry2(operation, opName, opts) {
10363
10517
  throw lastError instanceof Error ? lastError : new Error(String(lastError));
10364
10518
  }
10365
10519
  async function postJson2(url, body, headers, opts) {
10366
- const opName = `POST ${url}`;
10520
+ var _a;
10521
+ const opName = `POST ${redactUrlForLog(url)}`;
10522
+ const timeoutMs = (_a = opts.timeoutMs) != null ? _a : DEFAULT_REQUEST_TIMEOUT_MS;
10367
10523
  await withRetry2(
10368
10524
  async () => {
10369
10525
  const resp = await fetch(url, {
@@ -10372,7 +10528,8 @@ async function postJson2(url, body, headers, opts) {
10372
10528
  "Content-Type": "application/json",
10373
10529
  ...headers
10374
10530
  },
10375
- body: JSON.stringify(body)
10531
+ body: JSON.stringify(body),
10532
+ signal: AbortSignal.timeout(timeoutMs)
10376
10533
  });
10377
10534
  if (!resp.ok) {
10378
10535
  const text = await resp.text().catch(() => "");
@@ -10520,16 +10677,19 @@ function getActiveTraceContext() {
10520
10677
 
10521
10678
  // src/tracing/direct/shipper.ts
10522
10679
  function serializeAssociationValue(value) {
10523
- if (typeof value === "string") {
10524
- return value;
10525
- }
10526
- const jsonValue = JSON.stringify(value);
10527
- return jsonValue === void 0 ? String(value) : jsonValue;
10680
+ return stringifyBounded(value);
10528
10681
  }
10529
10682
  var DirectTraceShipper = class {
10530
10683
  constructor(opts) {
10531
10684
  this.queue = [];
10532
10685
  this.inFlight = /* @__PURE__ */ new Set();
10686
+ /**
10687
+ * Set once `shutdown()` begins and never cleared. Sends issued after the
10688
+ * drain window (stragglers, or flush work the deadline abandoned
10689
+ * mid-drain) run as a single short attempt instead of regaining the full
10690
+ * retry schedule.
10691
+ */
10692
+ this.hasShutdown = false;
10533
10693
  var _a, _b, _c, _d, _e, _f;
10534
10694
  this.writeKey = opts.writeKey;
10535
10695
  this.baseUrl = (_a = formatEndpoint2(opts.endpoint)) != null ? _a : "https://api.raindrop.ai/v1/";
@@ -10654,14 +10814,19 @@ var DirectTraceShipper = class {
10654
10814
  if (this.debug) console.log(`[raindrop] direct: dropped ${batch.length} cloud spans (local-only)`);
10655
10815
  continue;
10656
10816
  }
10817
+ const opts = this.requestOpts();
10818
+ if (!opts) {
10819
+ rateLimitedLog(
10820
+ "js-sdk.direct.shutdown_deadline",
10821
+ () => console.warn(
10822
+ `[raindrop] direct: shutdown flush deadline exceeded; dropping ${batch.length} spans`
10823
+ )
10824
+ );
10825
+ continue;
10826
+ }
10657
10827
  const body = buildExportTraceServiceRequest2(batch);
10658
10828
  const url = `${this.baseUrl}traces`;
10659
- const p = postJson2(
10660
- url,
10661
- body,
10662
- { Authorization: `Bearer ${this.writeKey}` },
10663
- { maxAttempts: 3, debug: this.debug }
10664
- );
10829
+ const p = postJson2(url, body, { Authorization: `Bearer ${this.writeKey}` }, opts);
10665
10830
  this.inFlight.add(p);
10666
10831
  try {
10667
10832
  try {
@@ -10678,14 +10843,48 @@ var DirectTraceShipper = class {
10678
10843
  }
10679
10844
  }
10680
10845
  }
10846
+ /**
10847
+ * Retry/timeout options for one batch POST, honoring the shutdown budget:
10848
+ * outside shutdown, the normal 3-attempt schedule; during shutdown, a
10849
+ * single attempt clamped to the remaining window; `null` once the window
10850
+ * is exhausted (caller drops the batch). After shutdown() returns,
10851
+ * stragglers run as a single short attempt.
10852
+ */
10853
+ requestOpts() {
10854
+ if (this.shutdownDeadlineAt !== void 0) {
10855
+ const remainingMs = this.shutdownDeadlineAt - Date.now();
10856
+ if (remainingMs <= 0) return null;
10857
+ return {
10858
+ maxAttempts: 1,
10859
+ debug: this.debug,
10860
+ timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
10861
+ };
10862
+ }
10863
+ if (this.hasShutdown) {
10864
+ return { maxAttempts: 1, debug: this.debug, timeoutMs: POST_SHUTDOWN_TIMEOUT_MS };
10865
+ }
10866
+ return { maxAttempts: 3, debug: this.debug };
10867
+ }
10681
10868
  async shutdown() {
10682
- if (this.timer) {
10683
- clearTimeout(this.timer);
10684
- this.timer = void 0;
10869
+ this.hasShutdown = true;
10870
+ this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
10871
+ try {
10872
+ if (this.timer) {
10873
+ clearTimeout(this.timer);
10874
+ this.timer = void 0;
10875
+ }
10876
+ const drain = async () => {
10877
+ await this.flush();
10878
+ await Promise.all([...this.inFlight].map((p) => p.catch(() => {
10879
+ })));
10880
+ };
10881
+ const settled = await raceWithTimeout(drain(), SHUTDOWN_DEADLINE_MS);
10882
+ if (!settled && this.debug) {
10883
+ console.warn("[raindrop] direct: shutdown deadline exceeded; abandoning in-flight spans");
10884
+ }
10885
+ } finally {
10886
+ this.shutdownDeadlineAt = void 0;
10685
10887
  }
10686
- await this.flush();
10687
- await Promise.all([...this.inFlight].map((p) => p.catch(() => {
10688
- })));
10689
10888
  }
10690
10889
  };
10691
10890
 
@@ -10701,7 +10900,7 @@ function getPropertiesFromContext(context5) {
10701
10900
  ...context5.properties || {}
10702
10901
  };
10703
10902
  if (context5.attachments && context5.attachments.length > 0) {
10704
- properties.attachments = JSON.stringify(context5.attachments);
10903
+ properties.attachments = stringifyBounded(context5.attachments);
10705
10904
  }
10706
10905
  return properties;
10707
10906
  }
@@ -10719,11 +10918,7 @@ function serializeAssociationProperties(properties) {
10719
10918
  );
10720
10919
  }
10721
10920
  function serializeSpanValue(value) {
10722
- if (typeof value === "string") {
10723
- return value;
10724
- }
10725
- const jsonValue = JSON.stringify(value);
10726
- return jsonValue === void 0 ? String(value) : jsonValue;
10921
+ return stringifyBounded(value);
10727
10922
  }
10728
10923
  function getLocalDebuggerMetadata(context5) {
10729
10924
  return {
@@ -11002,7 +11197,7 @@ var LiveInteraction = class {
11002
11197
  error = err instanceof Error ? err.message : String(err);
11003
11198
  const endTimeMs2 = Date.now();
11004
11199
  const durationMs2 = endTimeMs2 - startTimeMs;
11005
- const inputStr2 = params.inputParameters ? JSON.stringify(params.inputParameters) : void 0;
11200
+ const inputStr2 = params.inputParameters ? stringifyBounded(params.inputParameters) : void 0;
11006
11201
  this.directShipper.sendToolSpan({
11007
11202
  name: toolName,
11008
11203
  spanId: spanIdB64,
@@ -11031,8 +11226,8 @@ var LiveInteraction = class {
11031
11226
  }
11032
11227
  const endTimeMs = Date.now();
11033
11228
  const durationMs = endTimeMs - startTimeMs;
11034
- const inputStr = params.inputParameters ? JSON.stringify(params.inputParameters) : void 0;
11035
- const outputStr = result !== void 0 ? typeof result === "string" ? result : JSON.stringify(result) : void 0;
11229
+ const inputStr = params.inputParameters ? stringifyBounded(params.inputParameters) : void 0;
11230
+ const outputStr = result !== void 0 ? stringifyBounded(result) : void 0;
11036
11231
  this.directShipper.sendToolSpan({
11037
11232
  name: toolName,
11038
11233
  spanId: spanIdB64,
@@ -11269,8 +11464,8 @@ var LiveInteraction = class {
11269
11464
  const traceIdB64 = this.ensureTraceId(activeContext.traceIdB64);
11270
11465
  const { parentSpanIdB64 } = activeContext;
11271
11466
  const spanIdB64 = base64Encode2(randomBytes2(8));
11272
- const inputStr = input !== void 0 ? typeof input === "string" ? input : JSON.stringify(input) : void 0;
11273
- const outputStr = output !== void 0 ? typeof output === "string" ? output : JSON.stringify(output) : void 0;
11467
+ const inputStr = input !== void 0 ? stringifyBounded(input) : void 0;
11468
+ const outputStr = output !== void 0 ? stringifyBounded(output) : void 0;
11274
11469
  this.directShipper.sendToolSpan({
11275
11470
  name,
11276
11471
  spanId: spanIdB64,
@@ -11317,12 +11512,10 @@ var LiveInteraction = class {
11317
11512
  span.setAttribute("traceloop.span.kind", "tool");
11318
11513
  setAssociationProperties(span, properties);
11319
11514
  if (input !== void 0) {
11320
- const inputStr = typeof input === "string" ? input : JSON.stringify(input);
11321
- span.setAttribute("traceloop.entity.input", inputStr);
11515
+ span.setAttribute("traceloop.entity.input", stringifyBounded(input));
11322
11516
  }
11323
11517
  if (output !== void 0) {
11324
- const outputStr = typeof output === "string" ? output : JSON.stringify(output);
11325
- span.setAttribute("traceloop.entity.output", outputStr);
11518
+ span.setAttribute("traceloop.entity.output", stringifyBounded(output));
11326
11519
  }
11327
11520
  if (durationMs !== void 0) {
11328
11521
  span.setAttribute("traceloop.entity.duration_ms", durationMs);
@@ -11410,8 +11603,8 @@ var LiveTracer = class {
11410
11603
  if (this.directShipper) {
11411
11604
  const { traceIdB64, parentSpanIdB64 } = getActiveTraceContext();
11412
11605
  const spanIdB64 = base64Encode2(randomBytes2(8));
11413
- const inputStr = input !== void 0 ? typeof input === "string" ? input : JSON.stringify(input) : void 0;
11414
- const outputStr = output !== void 0 ? typeof output === "string" ? output : JSON.stringify(output) : void 0;
11606
+ const inputStr = input !== void 0 ? stringifyBounded(input) : void 0;
11607
+ const outputStr = output !== void 0 ? stringifyBounded(output) : void 0;
11415
11608
  this.directShipper.sendToolSpan({
11416
11609
  name,
11417
11610
  spanId: spanIdB64,
@@ -11455,12 +11648,10 @@ var LiveTracer = class {
11455
11648
  });
11456
11649
  span.setAttribute("traceloop.span.kind", "tool");
11457
11650
  if (input !== void 0) {
11458
- const inputStr = typeof input === "string" ? input : JSON.stringify(input);
11459
- span.setAttribute("traceloop.entity.input", inputStr);
11651
+ span.setAttribute("traceloop.entity.input", stringifyBounded(input));
11460
11652
  }
11461
11653
  if (output !== void 0) {
11462
- const outputStr = typeof output === "string" ? output : JSON.stringify(output);
11463
- span.setAttribute("traceloop.entity.output", outputStr);
11654
+ span.setAttribute("traceloop.entity.output", stringifyBounded(output));
11464
11655
  }
11465
11656
  if (durationMs !== void 0) {
11466
11657
  span.setAttribute("traceloop.entity.duration_ms", durationMs);
@@ -11892,6 +12083,7 @@ function getCurrentTraceId() {
11892
12083
  return (_a = import_api4.trace.getSpan(import_api4.context.active())) == null ? void 0 : _a.spanContext().traceId;
11893
12084
  }
11894
12085
  var tracing = (analytics, apiUrl, writeKey, options, isDebug = false) => {
12086
+ process.env.TRACELOOP_TELEMETRY = "false";
11895
12087
  const {
11896
12088
  logLevel,
11897
12089
  useExternalOtel,
@@ -12192,7 +12384,6 @@ var PartialClientAiTrack = ClientAiTrack.extend({
12192
12384
  var AttachmentTypeSchema = import_zod.z.enum(["code", "text", "image", "iframe"]);
12193
12385
 
12194
12386
  // src/index.ts
12195
- process.env.TRACELOOP_TELEMETRY = "false";
12196
12387
  var MAX_INGEST_SIZE_BYTES = 1 * 1024 * 1024;
12197
12388
  var SELF_DIAGNOSTICS_TOOL_NAME_DEFAULT = "__raindrop_report";
12198
12389
  var SELF_DIAGNOSTICS_SOURCE_DEFAULT = "agent_reporting_tool";
@@ -12317,12 +12508,12 @@ function parseRetryAfter3(headers) {
12317
12508
  }
12318
12509
  function getRetryDelayMs3(attemptNumber, previousError) {
12319
12510
  if (previousError && typeof previousError.retryAfterMs === "number") {
12320
- return Math.max(0, previousError.retryAfterMs);
12511
+ return Math.min(Math.max(0, previousError.retryAfterMs), MAX_RETRY_DELAY_MS);
12321
12512
  }
12322
12513
  if (attemptNumber <= 1) return 0;
12323
12514
  const base = 500;
12324
12515
  const factor = Math.pow(2, attemptNumber - 2);
12325
- return base * factor;
12516
+ return Math.min(base * factor, MAX_RETRY_DELAY_MS);
12326
12517
  }
12327
12518
  async function withRetry3(operation, opName, maxAttempts, debugLogs = false) {
12328
12519
  var _a;
@@ -12361,7 +12552,16 @@ var Raindrop = class {
12361
12552
  this.partialEventBuffer = /* @__PURE__ */ new Map();
12362
12553
  this.partialEventTimeouts = /* @__PURE__ */ new Map();
12363
12554
  this.inFlightRequests = /* @__PURE__ */ new Set();
12555
+ /**
12556
+ * Set once `close()` begins and never cleared. Sends issued after the
12557
+ * drain window (stragglers, or flush work the deadline abandoned
12558
+ * mid-drain) run as a single short attempt instead of regaining the full
12559
+ * retry schedule.
12560
+ */
12561
+ this.hasShutdown = false;
12364
12562
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
12563
+ setDefaultMaxTextFieldChars(config.maxTextFieldChars);
12564
+ this.maxTextFieldChars = resolveMaxTextFieldChars(config.maxTextFieldChars);
12365
12565
  this.writeKey = (_a = config.writeKey) != null ? _a : "";
12366
12566
  this.localDebuggerUrlOpt = config.localWorkshopUrl === false ? null : config.localWorkshopUrl;
12367
12567
  this.apiUrl = (_b = this.formatEndpoint(config.endpoint)) != null ? _b : `https://api.raindrop.ai/v1/`;
@@ -12584,14 +12784,16 @@ var Raindrop = class {
12584
12784
  eventId,
12585
12785
  userId,
12586
12786
  model,
12587
- input,
12588
- output,
12787
+ input: rawInput,
12788
+ output: rawOutput,
12589
12789
  convoId,
12590
12790
  attachments,
12591
12791
  properties,
12592
12792
  timestamp,
12593
12793
  ...rest
12594
12794
  } = e;
12795
+ const input = capText(rawInput, this.maxTextFieldChars);
12796
+ const output = capText(rawOutput, this.maxTextFieldChars);
12595
12797
  const customEventId = eventId != null ? eventId : crypto.randomUUID();
12596
12798
  const parsedAttachments = AttachmentSchema.array().safeParse(attachments != null ? attachments : []);
12597
12799
  if (!parsedAttachments.success) {
@@ -12798,20 +13000,51 @@ var Raindrop = class {
12798
13000
  void fetch(`${localUrl}${endpoint}`, {
12799
13001
  method: "POST",
12800
13002
  headers,
12801
- body: JSON.stringify(body)
13003
+ body: JSON.stringify(body),
13004
+ // Bounded like the cloud paths: a black-holed Workshop URL must not
13005
+ // pin connections indefinitely (fire-and-forget still holds sockets).
13006
+ signal: AbortSignal.timeout(DEFAULT_REQUEST_TIMEOUT_MS)
12802
13007
  }).catch(() => {
12803
13008
  });
12804
13009
  } catch (e) {
12805
13010
  }
12806
13011
  }
13012
+ /**
13013
+ * Attempts/timeout budget for one POST, honoring the close() deadline.
13014
+ * Returns `null` when the shutdown budget is exhausted — the caller must
13015
+ * drop the payload instead of issuing a request that could outlive
13016
+ * process exit. Checked fresh on every send so a deadline that expires
13017
+ * mid-drain takes effect immediately.
13018
+ */
13019
+ requestBudget() {
13020
+ if (this.shutdownDeadlineAt !== void 0) {
13021
+ const remainingMs = this.shutdownDeadlineAt - Date.now();
13022
+ if (remainingMs <= 0) return null;
13023
+ return { maxAttempts: 1, timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs) };
13024
+ }
13025
+ if (this.hasShutdown) {
13026
+ return { maxAttempts: 1, timeoutMs: POST_SHUTDOWN_TIMEOUT_MS };
13027
+ }
13028
+ return { maxAttempts: 3, timeoutMs: DEFAULT_REQUEST_TIMEOUT_MS };
13029
+ }
13030
+ warnShutdownDrop(what) {
13031
+ rateLimitedLog(
13032
+ "js-sdk.shutdown_deadline",
13033
+ () => console.warn(`[raindrop] shutdown flush deadline exceeded; dropping ${what}`)
13034
+ );
13035
+ }
12807
13036
  async sendBatchToApi(events, endpoint) {
12808
- var _a;
12809
13037
  const body = events.map(({ type, ...event }) => event);
12810
13038
  this.mirrorBatchToLocalDebugger(endpoint, body);
12811
13039
  if (this.localOnly) {
12812
13040
  return;
12813
13041
  }
12814
- const opName = `POST ${endpoint}`;
13042
+ const opName = `POST ${redactUrlForLog(this.apiUrl + endpoint)}`;
13043
+ const budget = this.requestBudget();
13044
+ if (!budget) {
13045
+ this.warnShutdownDrop(`${events.length} event(s) for ${endpoint}`);
13046
+ return;
13047
+ }
12815
13048
  try {
12816
13049
  const response = await withRetry3(
12817
13050
  async () => {
@@ -12821,7 +13054,11 @@ var Raindrop = class {
12821
13054
  "Content-Type": "application/json",
12822
13055
  Authorization: `Bearer ${this.writeKey}`
12823
13056
  },
12824
- body: JSON.stringify(body)
13057
+ body: JSON.stringify(body),
13058
+ // Bounded: a dead or black-holed endpoint must never pin this
13059
+ // request (and the flush/close paths awaiting it) until the OS
13060
+ // gives up.
13061
+ signal: AbortSignal.timeout(budget.timeoutMs)
12825
13062
  });
12826
13063
  if (!resp.ok) {
12827
13064
  const text = await resp.text().catch(() => "");
@@ -12835,7 +13072,7 @@ var Raindrop = class {
12835
13072
  return resp;
12836
13073
  },
12837
13074
  opName,
12838
- 3,
13075
+ budget.maxAttempts,
12839
13076
  this.debugLogs
12840
13077
  );
12841
13078
  if (this.debugLogs) {
@@ -12844,9 +13081,18 @@ var Raindrop = class {
12844
13081
  return response;
12845
13082
  } catch (error) {
12846
13083
  if (error == null ? void 0 : error.response) {
12847
- console.error(`[raindrop] Error in response (${opName}): ` + error.response.data);
13084
+ rateLimitedLog(
13085
+ `js-sdk.batch_failed.${endpoint}`,
13086
+ () => console.error(`[raindrop] Error in response (${opName}): ` + error.response.data)
13087
+ );
12848
13088
  } else {
12849
- console.error(`[raindrop] Error (${opName}): ` + ((_a = error == null ? void 0 : error.message) != null ? _a : String(error)));
13089
+ rateLimitedLog(
13090
+ `js-sdk.batch_failed.${endpoint}`,
13091
+ () => {
13092
+ var _a;
13093
+ return console.error(`[raindrop] Error (${opName}): ` + ((_a = error == null ? void 0 : error.message) != null ? _a : String(error)));
13094
+ }
13095
+ );
12850
13096
  }
12851
13097
  }
12852
13098
  }
@@ -12903,6 +13149,12 @@ var Raindrop = class {
12903
13149
  console.warn("[raindrop] trackAiPartial requires an eventId.");
12904
13150
  return Promise.resolve();
12905
13151
  }
13152
+ if (typeof rest.input === "string") {
13153
+ rest.input = capText(rest.input, this.maxTextFieldChars);
13154
+ }
13155
+ if (typeof rest.output === "string") {
13156
+ rest.output = capText(rest.output, this.maxTextFieldChars);
13157
+ }
12906
13158
  const existingEvent = this.partialEventBuffer.get(eventId) || {};
12907
13159
  const mergedEvent = this.deepMergeObjects(
12908
13160
  { ...existingEvent },
@@ -12933,7 +13185,7 @@ var Raindrop = class {
12933
13185
  this.partialEventTimeouts.set(eventId, newTimeout);
12934
13186
  if (this.debugLogs) {
12935
13187
  console.log(
12936
- `[raindrop] Updated partial event buffer for eventId: ${eventId}. Timeout reset (20s). Event: ${JSON.stringify(mergedEvent)}`
13188
+ `[raindrop] Updated partial event buffer for eventId: ${eventId}. Timeout reset (2s). Event: ${stringifyBounded(mergedEvent, 2e3)}`
12937
13189
  );
12938
13190
  }
12939
13191
  return Promise.resolve();
@@ -12949,9 +13201,12 @@ var Raindrop = class {
12949
13201
  try {
12950
13202
  await flushPromise;
12951
13203
  } catch (error) {
12952
- console.error(
12953
- `[raindrop] Failed to flush partial event ${eventId}:`,
12954
- error instanceof Error ? error : String(error)
13204
+ rateLimitedLog(
13205
+ "js-sdk.partial_flush_failed",
13206
+ () => console.error(
13207
+ `[raindrop] Failed to flush partial event ${eventId}:`,
13208
+ error instanceof Error ? error : String(error)
13209
+ )
12955
13210
  );
12956
13211
  } finally {
12957
13212
  this.inFlightRequests.delete(flushPromise);
@@ -13019,7 +13274,7 @@ var Raindrop = class {
13019
13274
  const parsed = PartialClientAiTrack.partial().safeParse(eventToSend);
13020
13275
  if (!parsed.success) {
13021
13276
  console.warn(
13022
- `[raindrop] Invalid accumulated data for partial eventId ${eventId}. Event not sent. Errors: ${this.formatZodError(parsed.error)} Data: ${JSON.stringify(eventToSend)}`
13277
+ `[raindrop] Invalid accumulated data for partial eventId ${eventId}. Event not sent. Errors: ${this.formatZodError(parsed.error)} Data: ${stringifyBounded(eventToSend, 2e3)}`
13023
13278
  );
13024
13279
  return;
13025
13280
  }
@@ -13043,7 +13298,6 @@ var Raindrop = class {
13043
13298
  * @param event - The event data conforming to ClientAiTrack schema.
13044
13299
  */
13045
13300
  async sendPartialEvent(event) {
13046
- var _a;
13047
13301
  const endpoint = "events/track_partial";
13048
13302
  const opName = `POST ${endpoint}`;
13049
13303
  mirrorPartialEventToLocalDebugger(event, {
@@ -13055,6 +13309,11 @@ var Raindrop = class {
13055
13309
  if (this.localOnly) {
13056
13310
  return;
13057
13311
  }
13312
+ const budget = this.requestBudget();
13313
+ if (!budget) {
13314
+ this.warnShutdownDrop(`partial event ${event.event_id}`);
13315
+ return;
13316
+ }
13058
13317
  try {
13059
13318
  const response = await withRetry3(
13060
13319
  async () => {
@@ -13064,8 +13323,10 @@ var Raindrop = class {
13064
13323
  "Content-Type": "application/json",
13065
13324
  Authorization: `Bearer ${this.writeKey}`
13066
13325
  },
13067
- body: JSON.stringify(event)
13326
+ body: JSON.stringify(event),
13068
13327
  // Send the single event object
13328
+ // Bounded: see sendBatchToApi.
13329
+ signal: AbortSignal.timeout(budget.timeoutMs)
13069
13330
  });
13070
13331
  if (!resp.ok) {
13071
13332
  const errorBody = await resp.text().catch(() => "");
@@ -13079,7 +13340,7 @@ var Raindrop = class {
13079
13340
  return resp;
13080
13341
  },
13081
13342
  opName,
13082
- 3,
13343
+ budget.maxAttempts,
13083
13344
  this.debugLogs
13084
13345
  );
13085
13346
  if (this.debugLogs) {
@@ -13089,8 +13350,14 @@ var Raindrop = class {
13089
13350
  }
13090
13351
  return response;
13091
13352
  } catch (error) {
13092
- console.error(
13093
- `[raindrop] Failed to send partial event ${event.event_id} to ${endpoint}: ${(_a = error == null ? void 0 : error.message) != null ? _a : String(error)}`
13353
+ rateLimitedLog(
13354
+ "js-sdk.partial_failed",
13355
+ () => {
13356
+ var _a;
13357
+ return console.error(
13358
+ `[raindrop] Failed to send partial event ${event.event_id} to ${endpoint}: ${(_a = error == null ? void 0 : error.message) != null ? _a : String(error)}`
13359
+ );
13360
+ }
13094
13361
  );
13095
13362
  throw error;
13096
13363
  }
@@ -13109,7 +13376,21 @@ var Raindrop = class {
13109
13376
  }
13110
13377
  }
13111
13378
  async close() {
13112
- await this._tracing.close();
13379
+ this.hasShutdown = true;
13380
+ this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
13381
+ try {
13382
+ await this.closeWithinDeadline();
13383
+ } finally {
13384
+ this.shutdownDeadlineAt = void 0;
13385
+ }
13386
+ }
13387
+ async closeWithinDeadline() {
13388
+ const remainingMs = () => {
13389
+ var _a;
13390
+ return Math.max(0, ((_a = this.shutdownDeadlineAt) != null ? _a : Date.now()) - Date.now());
13391
+ };
13392
+ const tracingSettled = await raceWithTimeout(this._tracing.close(), remainingMs());
13393
+ if (!tracingSettled) this.warnShutdownDrop("in-flight trace flush");
13113
13394
  try {
13114
13395
  await this.flush();
13115
13396
  } catch (error) {
@@ -13138,7 +13419,12 @@ var Raindrop = class {
13138
13419
  );
13139
13420
  }
13140
13421
  try {
13141
- await Promise.all(Array.from(this.inFlightRequests));
13422
+ const settled = await raceWithTimeout(
13423
+ Promise.allSettled(Array.from(this.inFlightRequests)).then(() => {
13424
+ }),
13425
+ remainingMs()
13426
+ );
13427
+ if (!settled) this.warnShutdownDrop("in-flight partial event request(s)");
13142
13428
  } catch (e) {
13143
13429
  console.error(`[raindrop] Error waiting for in-flight requests:`, e);
13144
13430
  }