raindrop-ai 0.1.1 → 0.1.2-otelv2

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/README.md CHANGED
@@ -18,6 +18,27 @@ const raindrop = new Raindrop({
18
18
  });
19
19
  ```
20
20
 
21
+ ## Payload size limits
22
+
23
+ Text fields (ai input/output, tool span I/O, span content) are capped at
24
+ **1,000,000 characters per field by default** and truncated with a
25
+ `...[truncated by raindrop]` marker. The cap is enforced before (or during)
26
+ serialization, so oversized payloads cost the cap — not the payload — on your
27
+ event loop, and large events land truncated instead of being silently dropped
28
+ at the 1 MB ingest limit. Tune it via:
29
+
30
+ ```ts
31
+ const raindrop = new Raindrop({
32
+ writeKey: "...",
33
+ maxTextFieldChars: 250_000,
34
+ });
35
+ ```
36
+
37
+ A stricter `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var is still honored
38
+ for span content. All outbound HTTP carries finite timeouts, and
39
+ `raindrop.close()` runs under a 10s deadline so a dead network can never
40
+ wedge your process exit.
41
+
21
42
  ## Notes
22
43
 
23
44
  - This package preserves the current dist structure and public exports.
@@ -1,4 +1,6 @@
1
- // ../core/dist/chunk-OLYXZCOK.js
1
+ // ../core/dist/chunk-U5HUTMR5.js
2
+ var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
3
+ var MAX_RETRY_DELAY_MS = 3e4;
2
4
  function wait(ms) {
3
5
  return new Promise((resolve) => setTimeout(resolve, ms));
4
6
  }
@@ -6,6 +8,46 @@ function formatEndpoint(endpoint) {
6
8
  if (!endpoint) return void 0;
7
9
  return endpoint.endsWith("/") ? endpoint : `${endpoint}/`;
8
10
  }
11
+ function redactUrlForLog(url) {
12
+ try {
13
+ const parsed = new URL(url);
14
+ parsed.username = "";
15
+ parsed.password = "";
16
+ parsed.search = "";
17
+ parsed.hash = "";
18
+ return parsed.toString();
19
+ } catch (e) {
20
+ return "<unparseable-url>";
21
+ }
22
+ }
23
+ var RATE_LIMITED_LOG_INTERVAL_MS = 3e4;
24
+ var rateLimitedLogLast = /* @__PURE__ */ new Map();
25
+ function rateLimitedLog(key, log) {
26
+ const now = Date.now();
27
+ const last = rateLimitedLogLast.get(key);
28
+ if (last !== void 0 && now - last < RATE_LIMITED_LOG_INTERVAL_MS) {
29
+ return false;
30
+ }
31
+ rateLimitedLogLast.set(key, now);
32
+ log();
33
+ return true;
34
+ }
35
+ async function raceWithTimeout(promise, timeoutMs) {
36
+ let timer;
37
+ const settledInTime = await Promise.race([
38
+ promise.then(
39
+ () => true,
40
+ () => true
41
+ ),
42
+ new Promise((resolve) => {
43
+ var _a;
44
+ timer = setTimeout(() => resolve(false), Math.max(0, timeoutMs));
45
+ (_a = timer.unref) == null ? void 0 : _a.call(timer);
46
+ })
47
+ ]);
48
+ if (timer) clearTimeout(timer);
49
+ return settledInTime;
50
+ }
9
51
  function parseRetryAfter(headers) {
10
52
  var _a;
11
53
  const value = (_a = headers.get("Retry-After")) != null ? _a : headers.get("retry-after");
@@ -22,12 +64,12 @@ function parseRetryAfter(headers) {
22
64
  function getRetryDelayMs(attemptNumber, previousError) {
23
65
  if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
24
66
  const v = previousError.retryAfterMs;
25
- if (typeof v === "number") return Math.max(0, v);
67
+ if (typeof v === "number") return Math.min(Math.max(0, v), MAX_RETRY_DELAY_MS);
26
68
  }
27
69
  if (attemptNumber <= 1) return 0;
28
70
  const base = 500;
29
71
  const factor = Math.pow(2, attemptNumber - 2);
30
- return base * factor;
72
+ return Math.min(base * factor, MAX_RETRY_DELAY_MS);
31
73
  }
32
74
  async function withRetry(operation, opName, opts) {
33
75
  const prefix = opts.sdkName ? `[raindrop-ai/${opts.sdkName}]` : "[raindrop-ai/core]";
@@ -62,7 +104,9 @@ async function withRetry(operation, opName, opts) {
62
104
  throw lastError instanceof Error ? lastError : new Error(String(lastError));
63
105
  }
64
106
  async function postJson(url, body, headers, opts) {
65
- const opName = `POST ${url}`;
107
+ var _a;
108
+ const opName = `POST ${redactUrlForLog(url)}`;
109
+ const timeoutMs = (_a = opts.timeoutMs) != null ? _a : DEFAULT_REQUEST_TIMEOUT_MS;
66
110
  await withRetry(
67
111
  async () => {
68
112
  const resp = await fetch(url, {
@@ -71,7 +115,8 @@ async function postJson(url, body, headers, opts) {
71
115
  "Content-Type": "application/json",
72
116
  ...headers
73
117
  },
74
- body: JSON.stringify(body)
118
+ body: JSON.stringify(body),
119
+ signal: AbortSignal.timeout(timeoutMs)
75
120
  });
76
121
  if (!resp.ok) {
77
122
  const text = await resp.text().catch(() => "");
@@ -88,6 +133,108 @@ async function postJson(url, body, headers, opts) {
88
133
  opts
89
134
  );
90
135
  }
136
+ var DEFAULT_MAX_TEXT_FIELD_CHARS = 1e6;
137
+ var TRUNCATION_MARKER = "...[truncated by raindrop]";
138
+ var BOUNDED_CLONE_MAX_DEPTH = 12;
139
+ var BOUNDED_CLONE_SLACK = 256;
140
+ var currentDefaultMaxTextFieldChars = DEFAULT_MAX_TEXT_FIELD_CHARS;
141
+ function setDefaultMaxTextFieldChars(value) {
142
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
143
+ currentDefaultMaxTextFieldChars = Math.floor(value);
144
+ }
145
+ }
146
+ function resolveMaxTextFieldChars(value) {
147
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
148
+ return Math.floor(value);
149
+ }
150
+ return currentDefaultMaxTextFieldChars;
151
+ }
152
+ function truncateToLimit(text, limit) {
153
+ if (limit > TRUNCATION_MARKER.length) {
154
+ return text.slice(0, limit - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;
155
+ }
156
+ return text.slice(0, Math.max(0, limit));
157
+ }
158
+ function capText(value, limit) {
159
+ if (typeof value !== "string") return value;
160
+ const max = limit != null ? limit : currentDefaultMaxTextFieldChars;
161
+ if (value.length <= max) return value;
162
+ return truncateToLimit(value, max);
163
+ }
164
+ function boundedClone(obj, charBudget) {
165
+ let budget = charBudget;
166
+ const seen = /* @__PURE__ */ new WeakSet();
167
+ const walk = (node, depth) => {
168
+ if (budget <= 0) return TRUNCATION_MARKER;
169
+ if (typeof node === "string") {
170
+ if (node.length > budget) {
171
+ const taken = node.slice(0, Math.max(0, budget)) + TRUNCATION_MARKER;
172
+ budget = 0;
173
+ return taken;
174
+ }
175
+ budget -= Math.max(node.length, 1);
176
+ return node;
177
+ }
178
+ if (node === null || typeof node === "number" || typeof node === "boolean") {
179
+ budget -= 8;
180
+ return node;
181
+ }
182
+ if (typeof node !== "object") {
183
+ budget -= 8;
184
+ return node;
185
+ }
186
+ if (seen.has(node)) return "[CIRCULAR]";
187
+ if (depth >= BOUNDED_CLONE_MAX_DEPTH) {
188
+ budget -= 16;
189
+ return `<max depth>`;
190
+ }
191
+ seen.add(node);
192
+ if (Array.isArray(node)) {
193
+ const out2 = [];
194
+ for (const v of node) {
195
+ if (budget <= 0) {
196
+ out2.push(TRUNCATION_MARKER);
197
+ break;
198
+ }
199
+ out2.push(walk(v, depth + 1));
200
+ }
201
+ return out2;
202
+ }
203
+ if (typeof node.toJSON === "function") {
204
+ budget -= 16;
205
+ return node;
206
+ }
207
+ const out = {};
208
+ for (const k in node) {
209
+ if (!Object.prototype.hasOwnProperty.call(node, k)) continue;
210
+ if (budget <= 0) {
211
+ out["..."] = TRUNCATION_MARKER;
212
+ break;
213
+ }
214
+ const key = walk(k, depth + 1);
215
+ out[key] = walk(node[k], depth + 1);
216
+ }
217
+ return out;
218
+ };
219
+ return walk(obj, 0);
220
+ }
221
+ function stringifyBounded(value, limit) {
222
+ const max = limit != null ? limit : currentDefaultMaxTextFieldChars;
223
+ if (typeof value === "string") {
224
+ return capText(value, max);
225
+ }
226
+ const pruned = boundedClone(value, max + TRUNCATION_MARKER.length + BOUNDED_CLONE_SLACK);
227
+ let json;
228
+ try {
229
+ json = JSON.stringify(pruned);
230
+ } catch (e) {
231
+ json = void 0;
232
+ }
233
+ if (json === void 0) {
234
+ return capText(String(value), max);
235
+ }
236
+ return json.length <= max ? json : truncateToLimit(json, max);
237
+ }
91
238
  var LOCAL_DEBUGGER_ENV_VAR = "RAINDROP_LOCAL_DEBUGGER";
92
239
  var WORKSHOP_ENV_VAR = "RAINDROP_WORKSHOP";
93
240
  var DEFAULT_LOCAL_WORKSHOP_URL = "http://localhost:5899/v1/";
@@ -196,6 +343,13 @@ function sendLocalDebuggerLiveEvent(event, options = {}) {
196
343
  ).catch(() => {
197
344
  });
198
345
  }
346
+ var SHUTDOWN_DEADLINE_MS = 1e4;
347
+ var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
348
+ var DEFAULT_REDACT_ATTRIBUTE_KEYS = [
349
+ "ai.request.providerOptions",
350
+ "ai.response.providerMetadata"
351
+ ];
352
+ var REDACT_JSON_ATTRIBUTE_KEYS = new Set(DEFAULT_REDACT_ATTRIBUTE_KEYS);
199
353
 
200
354
  // ../core/dist/index.node.js
201
355
  import { AsyncLocalStorage } from "async_hooks";
@@ -204,7 +358,7 @@ globalThis.RAINDROP_ASYNC_LOCAL_STORAGE = AsyncLocalStorage;
204
358
  // package.json
205
359
  var package_default = {
206
360
  name: "raindrop-ai",
207
- version: "0.1.1",
361
+ version: "0.1.2",
208
362
  main: "dist/index.js",
209
363
  module: "dist/index.mjs",
210
364
  types: "dist/index.d.ts",
@@ -362,12 +516,12 @@ function parseRetryAfter2(headers) {
362
516
  function getRetryDelayMs2(attemptNumber, previousError) {
363
517
  if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
364
518
  const v = previousError.retryAfterMs;
365
- if (typeof v === "number") return Math.max(0, v);
519
+ if (typeof v === "number") return Math.min(Math.max(0, v), MAX_RETRY_DELAY_MS);
366
520
  }
367
521
  if (attemptNumber <= 1) return 0;
368
522
  const base = 500;
369
523
  const factor = Math.pow(2, attemptNumber - 2);
370
- return base * factor;
524
+ return Math.min(base * factor, MAX_RETRY_DELAY_MS);
371
525
  }
372
526
  async function withRetry2(operation, opName, opts) {
373
527
  let lastError = void 0;
@@ -399,7 +553,9 @@ async function withRetry2(operation, opName, opts) {
399
553
  throw lastError instanceof Error ? lastError : new Error(String(lastError));
400
554
  }
401
555
  async function postJson2(url, body, headers, opts) {
402
- const opName = `POST ${url}`;
556
+ var _a;
557
+ const opName = `POST ${redactUrlForLog(url)}`;
558
+ const timeoutMs = (_a = opts.timeoutMs) != null ? _a : DEFAULT_REQUEST_TIMEOUT_MS;
403
559
  await withRetry2(
404
560
  async () => {
405
561
  const resp = await fetch(url, {
@@ -408,7 +564,8 @@ async function postJson2(url, body, headers, opts) {
408
564
  "Content-Type": "application/json",
409
565
  ...headers
410
566
  },
411
- body: JSON.stringify(body)
567
+ body: JSON.stringify(body),
568
+ signal: AbortSignal.timeout(timeoutMs)
412
569
  });
413
570
  if (!resp.ok) {
414
571
  const text = await resp.text().catch(() => "");
@@ -556,16 +713,19 @@ function getActiveTraceContext() {
556
713
 
557
714
  // src/tracing/direct/shipper.ts
558
715
  function serializeAssociationValue(value) {
559
- if (typeof value === "string") {
560
- return value;
561
- }
562
- const jsonValue = JSON.stringify(value);
563
- return jsonValue === void 0 ? String(value) : jsonValue;
716
+ return stringifyBounded(value);
564
717
  }
565
718
  var DirectTraceShipper = class {
566
719
  constructor(opts) {
567
720
  this.queue = [];
568
721
  this.inFlight = /* @__PURE__ */ new Set();
722
+ /**
723
+ * Set once `shutdown()` begins and never cleared. Sends issued after the
724
+ * drain window (stragglers, or flush work the deadline abandoned
725
+ * mid-drain) run as a single short attempt instead of regaining the full
726
+ * retry schedule.
727
+ */
728
+ this.hasShutdown = false;
569
729
  var _a, _b, _c, _d, _e, _f;
570
730
  this.writeKey = opts.writeKey;
571
731
  this.baseUrl = (_a = formatEndpoint2(opts.endpoint)) != null ? _a : "https://api.raindrop.ai/v1/";
@@ -690,14 +850,19 @@ var DirectTraceShipper = class {
690
850
  if (this.debug) console.log(`[raindrop] direct: dropped ${batch.length} cloud spans (local-only)`);
691
851
  continue;
692
852
  }
853
+ const opts = this.requestOpts();
854
+ if (!opts) {
855
+ rateLimitedLog(
856
+ "js-sdk.direct.shutdown_deadline",
857
+ () => console.warn(
858
+ `[raindrop] direct: shutdown flush deadline exceeded; dropping ${batch.length} spans`
859
+ )
860
+ );
861
+ continue;
862
+ }
693
863
  const body = buildExportTraceServiceRequest2(batch);
694
864
  const url = `${this.baseUrl}traces`;
695
- const p = postJson2(
696
- url,
697
- body,
698
- { Authorization: `Bearer ${this.writeKey}` },
699
- { maxAttempts: 3, debug: this.debug }
700
- );
865
+ const p = postJson2(url, body, { Authorization: `Bearer ${this.writeKey}` }, opts);
701
866
  this.inFlight.add(p);
702
867
  try {
703
868
  try {
@@ -714,14 +879,48 @@ var DirectTraceShipper = class {
714
879
  }
715
880
  }
716
881
  }
882
+ /**
883
+ * Retry/timeout options for one batch POST, honoring the shutdown budget:
884
+ * outside shutdown, the normal 3-attempt schedule; during shutdown, a
885
+ * single attempt clamped to the remaining window; `null` once the window
886
+ * is exhausted (caller drops the batch). After shutdown() returns,
887
+ * stragglers run as a single short attempt.
888
+ */
889
+ requestOpts() {
890
+ if (this.shutdownDeadlineAt !== void 0) {
891
+ const remainingMs = this.shutdownDeadlineAt - Date.now();
892
+ if (remainingMs <= 0) return null;
893
+ return {
894
+ maxAttempts: 1,
895
+ debug: this.debug,
896
+ timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
897
+ };
898
+ }
899
+ if (this.hasShutdown) {
900
+ return { maxAttempts: 1, debug: this.debug, timeoutMs: POST_SHUTDOWN_TIMEOUT_MS };
901
+ }
902
+ return { maxAttempts: 3, debug: this.debug };
903
+ }
717
904
  async shutdown() {
718
- if (this.timer) {
719
- clearTimeout(this.timer);
720
- this.timer = void 0;
905
+ this.hasShutdown = true;
906
+ this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
907
+ try {
908
+ if (this.timer) {
909
+ clearTimeout(this.timer);
910
+ this.timer = void 0;
911
+ }
912
+ const drain = async () => {
913
+ await this.flush();
914
+ await Promise.all([...this.inFlight].map((p) => p.catch(() => {
915
+ })));
916
+ };
917
+ const settled = await raceWithTimeout(drain(), SHUTDOWN_DEADLINE_MS);
918
+ if (!settled && this.debug) {
919
+ console.warn("[raindrop] direct: shutdown deadline exceeded; abandoning in-flight spans");
920
+ }
921
+ } finally {
922
+ this.shutdownDeadlineAt = void 0;
721
923
  }
722
- await this.flush();
723
- await Promise.all([...this.inFlight].map((p) => p.catch(() => {
724
- })));
725
924
  }
726
925
  };
727
926
 
@@ -743,7 +942,7 @@ function getPropertiesFromContext(context5) {
743
942
  ...context5.properties || {}
744
943
  };
745
944
  if (context5.attachments && context5.attachments.length > 0) {
746
- properties.attachments = JSON.stringify(context5.attachments);
945
+ properties.attachments = stringifyBounded(context5.attachments);
747
946
  }
748
947
  return properties;
749
948
  }
@@ -761,11 +960,7 @@ function serializeAssociationProperties(properties) {
761
960
  );
762
961
  }
763
962
  function serializeSpanValue(value) {
764
- if (typeof value === "string") {
765
- return value;
766
- }
767
- const jsonValue = JSON.stringify(value);
768
- return jsonValue === void 0 ? String(value) : jsonValue;
963
+ return stringifyBounded(value);
769
964
  }
770
965
  function getLocalDebuggerMetadata(context5) {
771
966
  return {
@@ -1044,7 +1239,7 @@ var LiveInteraction = class {
1044
1239
  error = err instanceof Error ? err.message : String(err);
1045
1240
  const endTimeMs2 = Date.now();
1046
1241
  const durationMs2 = endTimeMs2 - startTimeMs;
1047
- const inputStr2 = params.inputParameters ? JSON.stringify(params.inputParameters) : void 0;
1242
+ const inputStr2 = params.inputParameters ? stringifyBounded(params.inputParameters) : void 0;
1048
1243
  this.directShipper.sendToolSpan({
1049
1244
  name: toolName,
1050
1245
  spanId: spanIdB64,
@@ -1073,8 +1268,8 @@ var LiveInteraction = class {
1073
1268
  }
1074
1269
  const endTimeMs = Date.now();
1075
1270
  const durationMs = endTimeMs - startTimeMs;
1076
- const inputStr = params.inputParameters ? JSON.stringify(params.inputParameters) : void 0;
1077
- const outputStr = result !== void 0 ? typeof result === "string" ? result : JSON.stringify(result) : void 0;
1271
+ const inputStr = params.inputParameters ? stringifyBounded(params.inputParameters) : void 0;
1272
+ const outputStr = result !== void 0 ? stringifyBounded(result) : void 0;
1078
1273
  this.directShipper.sendToolSpan({
1079
1274
  name: toolName,
1080
1275
  spanId: spanIdB64,
@@ -1311,8 +1506,8 @@ var LiveInteraction = class {
1311
1506
  const traceIdB64 = this.ensureTraceId(activeContext.traceIdB64);
1312
1507
  const { parentSpanIdB64 } = activeContext;
1313
1508
  const spanIdB64 = base64Encode2(randomBytes2(8));
1314
- const inputStr = input !== void 0 ? typeof input === "string" ? input : JSON.stringify(input) : void 0;
1315
- const outputStr = output !== void 0 ? typeof output === "string" ? output : JSON.stringify(output) : void 0;
1509
+ const inputStr = input !== void 0 ? stringifyBounded(input) : void 0;
1510
+ const outputStr = output !== void 0 ? stringifyBounded(output) : void 0;
1316
1511
  this.directShipper.sendToolSpan({
1317
1512
  name,
1318
1513
  spanId: spanIdB64,
@@ -1359,12 +1554,10 @@ var LiveInteraction = class {
1359
1554
  span.setAttribute("traceloop.span.kind", "tool");
1360
1555
  setAssociationProperties(span, properties);
1361
1556
  if (input !== void 0) {
1362
- const inputStr = typeof input === "string" ? input : JSON.stringify(input);
1363
- span.setAttribute("traceloop.entity.input", inputStr);
1557
+ span.setAttribute("traceloop.entity.input", stringifyBounded(input));
1364
1558
  }
1365
1559
  if (output !== void 0) {
1366
- const outputStr = typeof output === "string" ? output : JSON.stringify(output);
1367
- span.setAttribute("traceloop.entity.output", outputStr);
1560
+ span.setAttribute("traceloop.entity.output", stringifyBounded(output));
1368
1561
  }
1369
1562
  if (durationMs !== void 0) {
1370
1563
  span.setAttribute("traceloop.entity.duration_ms", durationMs);
@@ -1452,8 +1645,8 @@ var LiveTracer = class {
1452
1645
  if (this.directShipper) {
1453
1646
  const { traceIdB64, parentSpanIdB64 } = getActiveTraceContext();
1454
1647
  const spanIdB64 = base64Encode2(randomBytes2(8));
1455
- const inputStr = input !== void 0 ? typeof input === "string" ? input : JSON.stringify(input) : void 0;
1456
- const outputStr = output !== void 0 ? typeof output === "string" ? output : JSON.stringify(output) : void 0;
1648
+ const inputStr = input !== void 0 ? stringifyBounded(input) : void 0;
1649
+ const outputStr = output !== void 0 ? stringifyBounded(output) : void 0;
1457
1650
  this.directShipper.sendToolSpan({
1458
1651
  name,
1459
1652
  spanId: spanIdB64,
@@ -1497,12 +1690,10 @@ var LiveTracer = class {
1497
1690
  });
1498
1691
  span.setAttribute("traceloop.span.kind", "tool");
1499
1692
  if (input !== void 0) {
1500
- const inputStr = typeof input === "string" ? input : JSON.stringify(input);
1501
- span.setAttribute("traceloop.entity.input", inputStr);
1693
+ span.setAttribute("traceloop.entity.input", stringifyBounded(input));
1502
1694
  }
1503
1695
  if (output !== void 0) {
1504
- const outputStr = typeof output === "string" ? output : JSON.stringify(output);
1505
- span.setAttribute("traceloop.entity.output", outputStr);
1696
+ span.setAttribute("traceloop.entity.output", stringifyBounded(output));
1506
1697
  }
1507
1698
  if (durationMs !== void 0) {
1508
1699
  span.setAttribute("traceloop.entity.duration_ms", durationMs);
@@ -1934,6 +2125,7 @@ function getCurrentTraceId() {
1934
2125
  return (_a = trace4.getSpan(context4.active())) == null ? void 0 : _a.spanContext().traceId;
1935
2126
  }
1936
2127
  var tracing = (analytics, apiUrl, writeKey, options, isDebug = false) => {
2128
+ process.env.TRACELOOP_TELEMETRY = "false";
1937
2129
  const {
1938
2130
  logLevel,
1939
2131
  useExternalOtel,
@@ -2213,8 +2405,19 @@ var tracing = (analytics, apiUrl, writeKey, options, isDebug = false) => {
2213
2405
  };
2214
2406
 
2215
2407
  export {
2408
+ DEFAULT_REQUEST_TIMEOUT_MS,
2409
+ MAX_RETRY_DELAY_MS,
2410
+ redactUrlForLog,
2411
+ rateLimitedLog,
2412
+ raceWithTimeout,
2413
+ setDefaultMaxTextFieldChars,
2414
+ resolveMaxTextFieldChars,
2415
+ capText,
2416
+ stringifyBounded,
2216
2417
  resolveLocalDebuggerBaseUrl,
2217
2418
  mirrorPartialEventToLocalDebugger,
2419
+ SHUTDOWN_DEADLINE_MS,
2420
+ POST_SHUTDOWN_TIMEOUT_MS,
2218
2421
  package_default,
2219
2422
  tracing
2220
2423
  };
package/dist/index.d.mts CHANGED
@@ -735,6 +735,16 @@ interface AnalyticsConfig {
735
735
  debugLogs?: boolean;
736
736
  endpoint?: string;
737
737
  redactPii?: boolean;
738
+ /**
739
+ * Per-field character cap applied to ai input/output and serialized
740
+ * tool/span content BEFORE buffering or serialization, so oversized
741
+ * payloads cost the cap — not the payload — on the calling code path
742
+ * (`JSON.stringify` of a multi-MB payload blocks the event loop).
743
+ * Truncated fields end with `...[truncated by raindrop]` and never exceed
744
+ * the cap, marker included. Defaults to 1,000,000 (matching the Python SDK's
745
+ * `max_text_field_chars`).
746
+ */
747
+ maxTextFieldChars?: number;
738
748
  /**
739
749
  * Force-enable (or opt out of) Workshop / local-debugger mirroring.
740
750
  *
@@ -855,6 +865,20 @@ declare class Raindrop {
855
865
  private partialEventBuffer;
856
866
  private partialEventTimeouts;
857
867
  private inFlightRequests;
868
+ private maxTextFieldChars;
869
+ /**
870
+ * Epoch ms deadline while `close()` is draining; undefined otherwise.
871
+ * Checked before every POST issued during the final flush so a dead or
872
+ * slow network can never wedge process exit.
873
+ */
874
+ private shutdownDeadlineAt;
875
+ /**
876
+ * Set once `close()` begins and never cleared. Sends issued after the
877
+ * drain window (stragglers, or flush work the deadline abandoned
878
+ * mid-drain) run as a single short attempt instead of regaining the full
879
+ * retry schedule.
880
+ */
881
+ private hasShutdown;
858
882
  debugLogs: boolean;
859
883
  constructor(config: AnalyticsConfig);
860
884
  private get localOnly();
@@ -964,6 +988,15 @@ declare class Raindrop {
964
988
  private saveToBuffer;
965
989
  private flush;
966
990
  private mirrorBatchToLocalDebugger;
991
+ /**
992
+ * Attempts/timeout budget for one POST, honoring the close() deadline.
993
+ * Returns `null` when the shutdown budget is exhausted — the caller must
994
+ * drop the payload instead of issuing a request that could outlive
995
+ * process exit. Checked fresh on every send so a deadline that expires
996
+ * mid-drain takes effect immediately.
997
+ */
998
+ private requestBudget;
999
+ private warnShutdownDrop;
967
1000
  private sendBatchToApi;
968
1001
  private getContext;
969
1002
  private formatZodError;
@@ -1000,6 +1033,7 @@ declare class Raindrop {
1000
1033
  */
1001
1034
  forceFlush(): Promise<void>;
1002
1035
  close(): Promise<void>;
1036
+ private closeWithinDeadline;
1003
1037
  }
1004
1038
 
1005
1039
  export { type AiTrackEvent, type Attachment, type AttachmentType, type BeginInteractionOptions, type FinishInteractionOptions, type IdentifyEvent, type Interaction, type LocalDebuggerLiveEventInput, MAX_INGEST_SIZE_BYTES, type PartialAiTrackEvent, Raindrop, type RaindropProperties, type RaindropPropertyLeaf, type RaindropPropertyValue, type RaindropSpanProcessor, type SelfDiagnoseOptions, type SelfDiagnosticsExecuteContext, type SelfDiagnosticsExecuteResult, type SelfDiagnosticsSignalDefinition, type SelfDiagnosticsSignalDefinitions, type SelfDiagnosticsTool, type SelfDiagnosticsToolInputSchema, type SelfDiagnosticsToolOptions, type SignalEvent, type SpanParams, type ToolParams, type ToolSpan, type TraceContext, type Tracer, type TrackToolParams, type WorkflowParams, Raindrop as default };
package/dist/index.d.ts CHANGED
@@ -735,6 +735,16 @@ interface AnalyticsConfig {
735
735
  debugLogs?: boolean;
736
736
  endpoint?: string;
737
737
  redactPii?: boolean;
738
+ /**
739
+ * Per-field character cap applied to ai input/output and serialized
740
+ * tool/span content BEFORE buffering or serialization, so oversized
741
+ * payloads cost the cap — not the payload — on the calling code path
742
+ * (`JSON.stringify` of a multi-MB payload blocks the event loop).
743
+ * Truncated fields end with `...[truncated by raindrop]` and never exceed
744
+ * the cap, marker included. Defaults to 1,000,000 (matching the Python SDK's
745
+ * `max_text_field_chars`).
746
+ */
747
+ maxTextFieldChars?: number;
738
748
  /**
739
749
  * Force-enable (or opt out of) Workshop / local-debugger mirroring.
740
750
  *
@@ -855,6 +865,20 @@ declare class Raindrop {
855
865
  private partialEventBuffer;
856
866
  private partialEventTimeouts;
857
867
  private inFlightRequests;
868
+ private maxTextFieldChars;
869
+ /**
870
+ * Epoch ms deadline while `close()` is draining; undefined otherwise.
871
+ * Checked before every POST issued during the final flush so a dead or
872
+ * slow network can never wedge process exit.
873
+ */
874
+ private shutdownDeadlineAt;
875
+ /**
876
+ * Set once `close()` begins and never cleared. Sends issued after the
877
+ * drain window (stragglers, or flush work the deadline abandoned
878
+ * mid-drain) run as a single short attempt instead of regaining the full
879
+ * retry schedule.
880
+ */
881
+ private hasShutdown;
858
882
  debugLogs: boolean;
859
883
  constructor(config: AnalyticsConfig);
860
884
  private get localOnly();
@@ -964,6 +988,15 @@ declare class Raindrop {
964
988
  private saveToBuffer;
965
989
  private flush;
966
990
  private mirrorBatchToLocalDebugger;
991
+ /**
992
+ * Attempts/timeout budget for one POST, honoring the close() deadline.
993
+ * Returns `null` when the shutdown budget is exhausted — the caller must
994
+ * drop the payload instead of issuing a request that could outlive
995
+ * process exit. Checked fresh on every send so a deadline that expires
996
+ * mid-drain takes effect immediately.
997
+ */
998
+ private requestBudget;
999
+ private warnShutdownDrop;
967
1000
  private sendBatchToApi;
968
1001
  private getContext;
969
1002
  private formatZodError;
@@ -1000,6 +1033,7 @@ declare class Raindrop {
1000
1033
  */
1001
1034
  forceFlush(): Promise<void>;
1002
1035
  close(): Promise<void>;
1036
+ private closeWithinDeadline;
1003
1037
  }
1004
1038
 
1005
1039
  export { type AiTrackEvent, type Attachment, type AttachmentType, type BeginInteractionOptions, type FinishInteractionOptions, type IdentifyEvent, type Interaction, type LocalDebuggerLiveEventInput, MAX_INGEST_SIZE_BYTES, type PartialAiTrackEvent, Raindrop, type RaindropProperties, type RaindropPropertyLeaf, type RaindropPropertyValue, type RaindropSpanProcessor, type SelfDiagnoseOptions, type SelfDiagnosticsExecuteContext, type SelfDiagnosticsExecuteResult, type SelfDiagnosticsSignalDefinition, type SelfDiagnosticsSignalDefinitions, type SelfDiagnosticsTool, type SelfDiagnosticsToolInputSchema, type SelfDiagnosticsToolOptions, type SignalEvent, type SpanParams, type ToolParams, type ToolSpan, type TraceContext, type Tracer, type TrackToolParams, type WorkflowParams, Raindrop as default };