@raindrop-ai/ai-sdk 0.0.33 → 0.0.35

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.
@@ -4,7 +4,7 @@ var async_hooks = require('async_hooks');
4
4
 
5
5
  // src/index.node.ts
6
6
 
7
- // ../core/dist/chunk-VUNUOE2X.js
7
+ // ../core/dist/chunk-SK6EJEO7.js
8
8
  function getCrypto() {
9
9
  const c = globalThis.crypto;
10
10
  return c;
@@ -59,6 +59,8 @@ function base64Encode(bytes) {
59
59
  }
60
60
  return out;
61
61
  }
62
+ var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
63
+ var MAX_RETRY_DELAY_MS = 3e4;
62
64
  function wait(ms) {
63
65
  return new Promise((resolve) => setTimeout(resolve, ms));
64
66
  }
@@ -66,6 +68,46 @@ function formatEndpoint(endpoint) {
66
68
  if (!endpoint) return void 0;
67
69
  return endpoint.endsWith("/") ? endpoint : `${endpoint}/`;
68
70
  }
71
+ function redactUrlForLog(url) {
72
+ try {
73
+ const parsed = new URL(url);
74
+ parsed.username = "";
75
+ parsed.password = "";
76
+ parsed.search = "";
77
+ parsed.hash = "";
78
+ return parsed.toString();
79
+ } catch (e) {
80
+ return "<unparseable-url>";
81
+ }
82
+ }
83
+ var RATE_LIMITED_LOG_INTERVAL_MS = 3e4;
84
+ var rateLimitedLogLast = /* @__PURE__ */ new Map();
85
+ function rateLimitedLog(key, log) {
86
+ const now = Date.now();
87
+ const last = rateLimitedLogLast.get(key);
88
+ if (last !== void 0 && now - last < RATE_LIMITED_LOG_INTERVAL_MS) {
89
+ return false;
90
+ }
91
+ rateLimitedLogLast.set(key, now);
92
+ log();
93
+ return true;
94
+ }
95
+ async function raceWithTimeout(promise, timeoutMs) {
96
+ let timer;
97
+ const settledInTime = await Promise.race([
98
+ promise.then(
99
+ () => true,
100
+ () => true
101
+ ),
102
+ new Promise((resolve) => {
103
+ var _a;
104
+ timer = setTimeout(() => resolve(false), Math.max(0, timeoutMs));
105
+ (_a = timer.unref) == null ? void 0 : _a.call(timer);
106
+ })
107
+ ]);
108
+ if (timer) clearTimeout(timer);
109
+ return settledInTime;
110
+ }
69
111
  function parseRetryAfter(headers) {
70
112
  var _a;
71
113
  const value = (_a = headers.get("Retry-After")) != null ? _a : headers.get("retry-after");
@@ -82,12 +124,12 @@ function parseRetryAfter(headers) {
82
124
  function getRetryDelayMs(attemptNumber, previousError) {
83
125
  if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
84
126
  const v = previousError.retryAfterMs;
85
- if (typeof v === "number") return Math.max(0, v);
127
+ if (typeof v === "number") return Math.min(Math.max(0, v), MAX_RETRY_DELAY_MS);
86
128
  }
87
129
  if (attemptNumber <= 1) return 0;
88
130
  const base = 500;
89
131
  const factor = Math.pow(2, attemptNumber - 2);
90
- return base * factor;
132
+ return Math.min(base * factor, MAX_RETRY_DELAY_MS);
91
133
  }
92
134
  async function withRetry(operation, opName2, opts) {
93
135
  const prefix = opts.sdkName ? `[raindrop-ai/${opts.sdkName}]` : "[raindrop-ai/core]";
@@ -122,7 +164,9 @@ async function withRetry(operation, opName2, opts) {
122
164
  throw lastError instanceof Error ? lastError : new Error(String(lastError));
123
165
  }
124
166
  async function postJson(url, body, headers, opts) {
125
- const opName2 = `POST ${url}`;
167
+ var _a;
168
+ const opName2 = `POST ${redactUrlForLog(url)}`;
169
+ const timeoutMs = (_a = opts.timeoutMs) != null ? _a : DEFAULT_REQUEST_TIMEOUT_MS;
126
170
  await withRetry(
127
171
  async () => {
128
172
  const resp = await fetch(url, {
@@ -131,7 +175,8 @@ async function postJson(url, body, headers, opts) {
131
175
  "Content-Type": "application/json",
132
176
  ...headers
133
177
  },
134
- body: JSON.stringify(body)
178
+ body: JSON.stringify(body),
179
+ signal: AbortSignal.timeout(timeoutMs)
135
180
  });
136
181
  if (!resp.ok) {
137
182
  const text = await resp.text().catch(() => "");
@@ -148,6 +193,27 @@ async function postJson(url, body, headers, opts) {
148
193
  opts
149
194
  );
150
195
  }
196
+ var DEFAULT_MAX_TEXT_FIELD_CHARS = 1e6;
197
+ var TRUNCATION_MARKER = "...[truncated by raindrop]";
198
+ var currentDefaultMaxTextFieldChars = DEFAULT_MAX_TEXT_FIELD_CHARS;
199
+ function resolveMaxTextFieldChars(value) {
200
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
201
+ return Math.floor(value);
202
+ }
203
+ return currentDefaultMaxTextFieldChars;
204
+ }
205
+ function truncateToLimit(text, limit) {
206
+ if (limit > TRUNCATION_MARKER.length) {
207
+ return text.slice(0, limit - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;
208
+ }
209
+ return text.slice(0, Math.max(0, limit));
210
+ }
211
+ function capText(value, limit) {
212
+ if (typeof value !== "string") return value;
213
+ const max = limit != null ? limit : currentDefaultMaxTextFieldChars;
214
+ if (value.length <= max) return value;
215
+ return truncateToLimit(value, max);
216
+ }
151
217
  var SpanStatusCode = {
152
218
  UNSET: 0,
153
219
  ERROR: 2
@@ -335,6 +401,27 @@ function sendLocalDebuggerLiveEvent(event, options = {}) {
335
401
  ).catch(() => {
336
402
  });
337
403
  }
404
+ var PROJECT_ID_HEADER = "X-Raindrop-Project-Id";
405
+ var PROJECT_ID_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
406
+ function isValidProjectIdSlug(value) {
407
+ return PROJECT_ID_SLUG_PATTERN.test(value);
408
+ }
409
+ function normalizeProjectId(raw, opts) {
410
+ if (typeof raw !== "string") return void 0;
411
+ const trimmed = raw.trim();
412
+ if (!trimmed) return void 0;
413
+ if (!isValidProjectIdSlug(trimmed) && opts.debug) {
414
+ console.warn(
415
+ `${opts.prefix} projectId "${trimmed}" does not match slug ${PROJECT_ID_SLUG_PATTERN.source}; sending anyway \u2014 backend may reject with HTTP 400`
416
+ );
417
+ }
418
+ return trimmed;
419
+ }
420
+ function projectIdHeaders(projectId) {
421
+ return projectId ? { [PROJECT_ID_HEADER]: projectId } : {};
422
+ }
423
+ var SHUTDOWN_DEADLINE_MS = 1e4;
424
+ var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
338
425
  function mergePatches(target, source) {
339
426
  var _a, _b, _c, _d;
340
427
  const out = { ...target, ...source };
@@ -352,6 +439,7 @@ var EventShipper = class {
352
439
  this.sticky = /* @__PURE__ */ new Map();
353
440
  this.timers = /* @__PURE__ */ new Map();
354
441
  this.inFlight = /* @__PURE__ */ new Set();
442
+ this.hasShutdown = false;
355
443
  var _a, _b, _c, _d, _e, _f, _g, _h;
356
444
  this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
357
445
  this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
@@ -361,10 +449,15 @@ var EventShipper = class {
361
449
  this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
362
450
  this.prefix = `[raindrop-ai/${this.sdkName}]`;
363
451
  this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
452
+ this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
364
453
  this.localDebuggerUrl = (_f = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _f : void 0;
365
454
  if (this.debug && this.localDebuggerUrl) {
366
455
  console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
367
456
  }
457
+ this.projectId = normalizeProjectId(opts.projectId, {
458
+ debug: this.debug,
459
+ prefix: this.prefix
460
+ });
368
461
  const isNode = typeof process !== "undefined" && typeof process.version === "string";
369
462
  this.context = {
370
463
  library: {
@@ -383,10 +476,55 @@ var EventShipper = class {
383
476
  authHeaders() {
384
477
  return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
385
478
  }
479
+ requestHeaders() {
480
+ return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
481
+ }
482
+ /**
483
+ * Build the retry/timeout options for one POST, honoring the shutdown
484
+ * deadline. Returns `null` when the shutdown drain window is exhausted —
485
+ * the caller must drop the payload (with a rate-limited warning) instead
486
+ * of issuing a request that could outlive process exit.
487
+ *
488
+ * Checked fresh on EVERY send, so a shutdown that begins while the flush
489
+ * path is mid-drain takes effect immediately: no further retries, and the
490
+ * per-attempt timeout is clamped to the remaining window. After
491
+ * `shutdown()` returns (deadline cleared, `hasShutdown` still set),
492
+ * sends — late callers, or flush work the deadline abandoned mid-drain —
493
+ * run as a single short attempt rather than regaining the full retry
494
+ * schedule.
495
+ */
496
+ requestOpts() {
497
+ if (this.shutdownDeadlineAt !== void 0) {
498
+ const remainingMs = this.shutdownDeadlineAt - Date.now();
499
+ if (remainingMs <= 0) return null;
500
+ return {
501
+ maxAttempts: 1,
502
+ debug: this.debug,
503
+ sdkName: this.sdkName,
504
+ timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
505
+ };
506
+ }
507
+ if (this.hasShutdown) {
508
+ return {
509
+ maxAttempts: 1,
510
+ debug: this.debug,
511
+ sdkName: this.sdkName,
512
+ timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
513
+ };
514
+ }
515
+ return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
516
+ }
386
517
  async patch(eventId, patch) {
387
518
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
388
519
  if (!this.enabled) return;
389
520
  if (!eventId || !eventId.trim()) return;
521
+ const maxChars = resolveMaxTextFieldChars(this.maxTextFieldCharsOpt);
522
+ if (typeof patch.input === "string" && patch.input.length > maxChars) {
523
+ patch = { ...patch, input: capText(patch.input, maxChars) };
524
+ }
525
+ if (typeof patch.output === "string" && patch.output.length > maxChars) {
526
+ patch = { ...patch, output: capText(patch.output, maxChars) };
527
+ }
390
528
  if (this.debug) {
391
529
  console.log(`${this.prefix} queue patch`, {
392
530
  eventId,
@@ -433,9 +571,16 @@ var EventShipper = class {
433
571
  })));
434
572
  }
435
573
  async shutdown() {
436
- for (const t of this.timers.values()) clearTimeout(t);
437
- this.timers.clear();
438
- await this.flush();
574
+ this.hasShutdown = true;
575
+ this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
576
+ try {
577
+ for (const t of this.timers.values()) clearTimeout(t);
578
+ this.timers.clear();
579
+ const settled = await raceWithTimeout(this.flush(), SHUTDOWN_DEADLINE_MS);
580
+ if (!settled) this.warnShutdownDrop("in-flight request(s) at shutdown");
581
+ } finally {
582
+ this.shutdownDeadlineAt = void 0;
583
+ }
439
584
  }
440
585
  async trackSignal(signal) {
441
586
  var _a, _b;
@@ -457,15 +602,19 @@ var EventShipper = class {
457
602
  ];
458
603
  if (!this.writeKey) return;
459
604
  const url = `${this.baseUrl}signals/track`;
605
+ const opts = this.requestOpts();
606
+ if (!opts) {
607
+ this.warnShutdownDrop("signal");
608
+ return;
609
+ }
460
610
  try {
461
- await postJson(url, body, this.authHeaders(), {
462
- maxAttempts: 3,
463
- debug: this.debug,
464
- sdkName: this.sdkName
465
- });
611
+ await postJson(url, body, this.requestHeaders(), opts);
466
612
  } catch (err) {
467
613
  const msg = err instanceof Error ? err.message : String(err);
468
- console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`);
614
+ rateLimitedLog(
615
+ `${this.prefix}.send_signal_failed`,
616
+ () => console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`)
617
+ );
469
618
  }
470
619
  }
471
620
  async identify(users) {
@@ -489,17 +638,27 @@ var EventShipper = class {
489
638
  if (!this.writeKey) return;
490
639
  if (body.length === 0) return;
491
640
  const url = `${this.baseUrl}users/identify`;
641
+ const opts = this.requestOpts();
642
+ if (!opts) {
643
+ this.warnShutdownDrop("identify");
644
+ return;
645
+ }
492
646
  try {
493
- await postJson(url, body, this.authHeaders(), {
494
- maxAttempts: 3,
495
- debug: this.debug,
496
- sdkName: this.sdkName
497
- });
647
+ await postJson(url, body, this.requestHeaders(), opts);
498
648
  } catch (err) {
499
649
  const msg = err instanceof Error ? err.message : String(err);
500
- console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`);
650
+ rateLimitedLog(
651
+ `${this.prefix}.send_identify_failed`,
652
+ () => console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`)
653
+ );
501
654
  }
502
655
  }
656
+ warnShutdownDrop(what) {
657
+ rateLimitedLog(
658
+ `${this.prefix}.shutdown_deadline`,
659
+ () => console.warn(`${this.prefix} shutdown flush deadline exceeded; dropping ${what}`)
660
+ );
661
+ }
503
662
  async flushOne(eventId) {
504
663
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
505
664
  if (!this.enabled) return;
@@ -575,11 +734,13 @@ var EventShipper = class {
575
734
  if (!isPending) this.sticky.delete(eventId);
576
735
  return;
577
736
  }
578
- const p = postJson(url, payload, this.authHeaders(), {
579
- maxAttempts: 3,
580
- debug: this.debug,
581
- sdkName: this.sdkName
582
- });
737
+ const opts = this.requestOpts();
738
+ if (!opts) {
739
+ this.warnShutdownDrop(`track_partial ${eventId}`);
740
+ if (!isPending) this.sticky.delete(eventId);
741
+ return;
742
+ }
743
+ const p = postJson(url, payload, this.requestHeaders(), opts);
583
744
  this.inFlight.add(p);
584
745
  try {
585
746
  try {
@@ -589,7 +750,10 @@ var EventShipper = class {
589
750
  }
590
751
  } catch (err) {
591
752
  const msg = err instanceof Error ? err.message : String(err);
592
- console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`);
753
+ rateLimitedLog(
754
+ `${this.prefix}.send_track_partial_failed`,
755
+ () => console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`)
756
+ );
593
757
  }
594
758
  } finally {
595
759
  this.inFlight.delete(p);
@@ -688,10 +852,24 @@ function redactJsonAttributeValue(key, value) {
688
852
  if (scrubbedJson === json) return void 0;
689
853
  return { stringValue: scrubbedJson };
690
854
  }
855
+ function applyOtelSpanAttributeLimit(limit) {
856
+ var _a, _b;
857
+ try {
858
+ 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;
859
+ if (!raw) return limit;
860
+ const parsed = Number.parseInt(raw, 10);
861
+ if (Number.isFinite(parsed) && parsed > 0) {
862
+ return Math.min(limit, parsed);
863
+ }
864
+ } catch (e) {
865
+ }
866
+ return limit;
867
+ }
691
868
  var TraceShipper = class {
692
869
  constructor(opts) {
693
870
  this.queue = [];
694
871
  this.inFlight = /* @__PURE__ */ new Set();
872
+ this.hasShutdown = false;
695
873
  var _a, _b, _c, _d, _e, _f, _g, _h, _i;
696
874
  this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
697
875
  this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
@@ -709,8 +887,45 @@ var TraceShipper = class {
709
887
  if (this.debug && this.localDebuggerUrl) {
710
888
  console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
711
889
  }
890
+ this.projectId = normalizeProjectId(opts.projectId, {
891
+ debug: this.debug,
892
+ prefix: this.prefix
893
+ });
712
894
  this.transformSpanHook = opts.transformSpan;
713
895
  this.disableDefaultRedaction = opts.disableDefaultRedaction === true;
896
+ this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
897
+ }
898
+ /**
899
+ * Cap every string attribute value on the span. O(#attributes) length
900
+ * checks; only oversized values pay a slice. Runs AFTER the redaction
901
+ * pipeline so the default secret-scrub still sees parseable JSON in
902
+ * `ai.request.providerOptions` / `ai.response.providerMetadata` (capping
903
+ * first could cut a JSON blob mid-way, fail the parse, and ship secrets
904
+ * in the surviving prefix).
905
+ *
906
+ * A stricter `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var is honored
907
+ * for span content, matching the Python SDK and the OTel SDK convention.
908
+ */
909
+ capSpanAttributes(span) {
910
+ var _a;
911
+ const maxChars = applyOtelSpanAttributeLimit(
912
+ resolveMaxTextFieldChars(this.maxTextFieldCharsOpt)
913
+ );
914
+ const attrs = span.attributes;
915
+ if (!attrs || attrs.length === 0) return span;
916
+ let nextAttrs;
917
+ for (let i = 0; i < attrs.length; i++) {
918
+ const attr = attrs[i];
919
+ const value = (_a = attr.value) == null ? void 0 : _a.stringValue;
920
+ if (typeof value !== "string" || value.length <= maxChars) continue;
921
+ if (!nextAttrs) nextAttrs = attrs.slice();
922
+ nextAttrs[i] = {
923
+ key: attr.key,
924
+ value: { ...attr.value, stringValue: capText(value, maxChars) }
925
+ };
926
+ }
927
+ if (!nextAttrs) return span;
928
+ return { ...span, attributes: nextAttrs };
714
929
  }
715
930
  /**
716
931
  * Apply the user `transformSpan` hook (if any) followed by the default
@@ -744,7 +959,7 @@ var TraceShipper = class {
744
959
  if (!this.disableDefaultRedaction) {
745
960
  current = defaultTransformSpan(current);
746
961
  }
747
- return current;
962
+ return this.capSpanAttributes(current);
748
963
  }
749
964
  isDebugEnabled() {
750
965
  return this.debug;
@@ -752,6 +967,9 @@ var TraceShipper = class {
752
967
  authHeaders() {
753
968
  return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
754
969
  }
970
+ requestHeaders() {
971
+ return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
972
+ }
755
973
  startSpan(args) {
756
974
  var _a, _b;
757
975
  const ids = createSpanIds(args.parent);
@@ -865,6 +1083,16 @@ var TraceShipper = class {
865
1083
  while (this.queue.length > 0) {
866
1084
  const batch = this.queue.splice(0, this.maxBatchSize);
867
1085
  if (!this.writeKey) continue;
1086
+ const opts = this.requestOpts();
1087
+ if (!opts) {
1088
+ rateLimitedLog(
1089
+ `${this.prefix}.shutdown_deadline`,
1090
+ () => console.warn(
1091
+ `${this.prefix} shutdown flush deadline exceeded; dropping ${batch.length} spans`
1092
+ )
1093
+ );
1094
+ continue;
1095
+ }
868
1096
  const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
869
1097
  const url = `${this.baseUrl}traces`;
870
1098
  if (this.debug) {
@@ -873,11 +1101,7 @@ var TraceShipper = class {
873
1101
  endpoint: url
874
1102
  });
875
1103
  }
876
- const p = postJson(url, body, this.authHeaders(), {
877
- maxAttempts: 3,
878
- debug: this.debug,
879
- sdkName: this.sdkName
880
- });
1104
+ const p = postJson(url, body, this.requestHeaders(), opts);
881
1105
  this.inFlight.add(p);
882
1106
  try {
883
1107
  try {
@@ -885,21 +1109,61 @@ var TraceShipper = class {
885
1109
  if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
886
1110
  } catch (err) {
887
1111
  const msg = err instanceof Error ? err.message : String(err);
888
- console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`);
1112
+ rateLimitedLog(
1113
+ `${this.prefix}.send_spans_failed`,
1114
+ () => console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`)
1115
+ );
889
1116
  }
890
1117
  } finally {
891
1118
  this.inFlight.delete(p);
892
1119
  }
893
1120
  }
894
1121
  }
1122
+ /** See EventShipper.requestOpts — same shutdown-budget semantics. */
1123
+ requestOpts() {
1124
+ if (this.shutdownDeadlineAt !== void 0) {
1125
+ const remainingMs = this.shutdownDeadlineAt - Date.now();
1126
+ if (remainingMs <= 0) return null;
1127
+ return {
1128
+ maxAttempts: 1,
1129
+ debug: this.debug,
1130
+ sdkName: this.sdkName,
1131
+ timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
1132
+ };
1133
+ }
1134
+ if (this.hasShutdown) {
1135
+ return {
1136
+ maxAttempts: 1,
1137
+ debug: this.debug,
1138
+ sdkName: this.sdkName,
1139
+ timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
1140
+ };
1141
+ }
1142
+ return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
1143
+ }
895
1144
  async shutdown() {
896
- if (this.timer) {
897
- clearTimeout(this.timer);
898
- this.timer = void 0;
1145
+ this.hasShutdown = true;
1146
+ this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
1147
+ try {
1148
+ if (this.timer) {
1149
+ clearTimeout(this.timer);
1150
+ this.timer = void 0;
1151
+ }
1152
+ const drain = async () => {
1153
+ await this.flush();
1154
+ await Promise.all([...this.inFlight].map((p) => p.catch(() => {
1155
+ })));
1156
+ };
1157
+ const settled = await raceWithTimeout(drain(), SHUTDOWN_DEADLINE_MS);
1158
+ if (!settled) {
1159
+ rateLimitedLog(
1160
+ `${this.prefix}.shutdown_deadline`,
1161
+ () => console.warn(`${this.prefix} shutdown flush deadline exceeded; abandoning in-flight spans`)
1162
+ );
1163
+ }
1164
+ } finally {
1165
+ this.shutdownDeadlineAt = void 0;
899
1166
  }
900
- await this.flush();
901
- await Promise.all([...this.inFlight].map((p) => p.catch(() => {
902
- })));
903
1167
  }
904
1168
  };
905
1169
  var NOOP_SPAN = {
@@ -1024,7 +1288,7 @@ globalThis.RAINDROP_ASYNC_LOCAL_STORAGE = async_hooks.AsyncLocalStorage;
1024
1288
  // package.json
1025
1289
  var package_default = {
1026
1290
  name: "@raindrop-ai/ai-sdk",
1027
- version: "0.0.33"};
1291
+ version: "0.0.35"};
1028
1292
 
1029
1293
  // src/internal/version.ts
1030
1294
  var libraryName = package_default.name;
@@ -1043,6 +1307,133 @@ var EventShipper2 = class extends EventShipper {
1043
1307
  }
1044
1308
  };
1045
1309
 
1310
+ // src/internal/truncation.ts
1311
+ var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
1312
+ var DEFAULT_MAX_TEXT_FIELD_CHARS2 = 1e6;
1313
+ var MAX_DEPTH = 12;
1314
+ var configuredMaxTextFieldChars;
1315
+ function setMaxTextFieldChars(limit) {
1316
+ if (limit === void 0) return;
1317
+ if (typeof limit === "number" && Number.isFinite(limit) && limit > 0) {
1318
+ configuredMaxTextFieldChars = Math.floor(limit);
1319
+ } else {
1320
+ console.warn(`[raindrop-ai] maxTextFieldChars=${String(limit)} ignored; must be > 0`);
1321
+ }
1322
+ }
1323
+ function otelEnvLimit() {
1324
+ var _a;
1325
+ if (typeof process === "undefined") return void 0;
1326
+ const raw = (_a = process.env) == null ? void 0 : _a.OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT;
1327
+ if (!raw) return void 0;
1328
+ const parsed = Number.parseInt(raw, 10);
1329
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
1330
+ }
1331
+ function effectiveMaxTextFieldChars() {
1332
+ const base = configuredMaxTextFieldChars != null ? configuredMaxTextFieldChars : DEFAULT_MAX_TEXT_FIELD_CHARS2;
1333
+ const env = otelEnvLimit();
1334
+ return env !== void 0 && env < base ? env : base;
1335
+ }
1336
+ function truncateToLimit2(text, limit) {
1337
+ if (limit > TRUNCATION_MARKER2.length) {
1338
+ return text.slice(0, limit - TRUNCATION_MARKER2.length) + TRUNCATION_MARKER2;
1339
+ }
1340
+ return text.slice(0, limit);
1341
+ }
1342
+ function capText2(value, limit) {
1343
+ if (typeof value !== "string") return value;
1344
+ const max = limit != null ? limit : effectiveMaxTextFieldChars();
1345
+ if (value.length <= max) return value;
1346
+ return truncateToLimit2(value, max);
1347
+ }
1348
+ function boundedClone2(value, budget, depth) {
1349
+ var _a, _b;
1350
+ if (budget.remaining <= 0) return TRUNCATION_MARKER2;
1351
+ if (typeof value === "string") {
1352
+ if (value.length > budget.remaining) {
1353
+ const taken = value.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
1354
+ budget.remaining = 0;
1355
+ return taken;
1356
+ }
1357
+ budget.remaining -= Math.max(value.length, 1);
1358
+ return value;
1359
+ }
1360
+ if (value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
1361
+ budget.remaining -= 8;
1362
+ return value;
1363
+ }
1364
+ if (typeof value !== "object") {
1365
+ budget.remaining -= 1;
1366
+ return value;
1367
+ }
1368
+ if (value instanceof Uint8Array) {
1369
+ const maxBytes = Math.max(0, Math.ceil(budget.remaining * 3 / 4));
1370
+ if (value.byteLength > maxBytes) {
1371
+ const taken = base64Encode(value.subarray(0, maxBytes)) + TRUNCATION_MARKER2;
1372
+ budget.remaining = 0;
1373
+ return taken;
1374
+ }
1375
+ const encoded = base64Encode(value);
1376
+ budget.remaining -= Math.max(encoded.length, 1);
1377
+ return encoded;
1378
+ }
1379
+ if (depth >= MAX_DEPTH) {
1380
+ budget.remaining -= 16;
1381
+ const name = (_b = (_a = value.constructor) == null ? void 0 : _a.name) != null ? _b : "object";
1382
+ return `<max depth: ${name}>`;
1383
+ }
1384
+ const toJSON = value.toJSON;
1385
+ if (typeof toJSON === "function") {
1386
+ budget.remaining -= 2;
1387
+ return boundedClone2(toJSON.call(value), budget, depth + 1);
1388
+ }
1389
+ if (Array.isArray(value)) {
1390
+ budget.remaining -= 2;
1391
+ const out2 = [];
1392
+ for (const item of value) {
1393
+ if (budget.remaining <= 0) {
1394
+ out2.push(TRUNCATION_MARKER2);
1395
+ break;
1396
+ }
1397
+ out2.push(boundedClone2(item, budget, depth + 1));
1398
+ }
1399
+ return out2;
1400
+ }
1401
+ budget.remaining -= 2;
1402
+ const out = {};
1403
+ for (const key in value) {
1404
+ if (!Object.prototype.hasOwnProperty.call(value, key)) continue;
1405
+ if (budget.remaining <= 0) {
1406
+ out["..."] = TRUNCATION_MARKER2;
1407
+ break;
1408
+ }
1409
+ let cappedKey = key;
1410
+ if (key.length > budget.remaining) {
1411
+ cappedKey = key.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
1412
+ budget.remaining = 0;
1413
+ out[cappedKey] = TRUNCATION_MARKER2;
1414
+ break;
1415
+ }
1416
+ budget.remaining -= Math.max(key.length, 1);
1417
+ out[cappedKey] = boundedClone2(value[key], budget, depth + 1);
1418
+ }
1419
+ return out;
1420
+ }
1421
+ function boundedStringify(value, limit) {
1422
+ const max = limit != null ? limit : effectiveMaxTextFieldChars();
1423
+ try {
1424
+ if (typeof value === "string") {
1425
+ const text2 = JSON.stringify(capText2(value, max));
1426
+ return text2.length <= max ? text2 : truncateToLimit2(text2, max);
1427
+ }
1428
+ const pruned = boundedClone2(value, { remaining: max + TRUNCATION_MARKER2.length + 256 }, 0);
1429
+ const text = JSON.stringify(pruned);
1430
+ if (text === void 0) return void 0;
1431
+ return text.length <= max ? text : truncateToLimit2(text, max);
1432
+ } catch (e) {
1433
+ return void 0;
1434
+ }
1435
+ }
1436
+
1046
1437
  // src/internal/wrap/helpers.ts
1047
1438
  function isRecord(value) {
1048
1439
  return typeof value === "object" && value !== null;
@@ -1067,21 +1458,10 @@ function isModuleNamespace(obj) {
1067
1458
  }
1068
1459
  }
1069
1460
  function safeJson(value) {
1070
- try {
1071
- return JSON.stringify(value);
1072
- } catch (e) {
1073
- return void 0;
1074
- }
1461
+ return boundedStringify(value);
1075
1462
  }
1076
1463
  function safeJsonWithUint8(value) {
1077
- try {
1078
- return JSON.stringify(value, (_key, v) => {
1079
- if (v instanceof Uint8Array) return base64Encode(v);
1080
- return v;
1081
- });
1082
- } catch (e) {
1083
- return void 0;
1084
- }
1464
+ return boundedStringify(value);
1085
1465
  }
1086
1466
  function extractModelInfo(model) {
1087
1467
  if (typeof model === "string") {
@@ -1977,7 +2357,10 @@ var RaindropTelemetryIntegration = class {
1977
2357
  inputAttrs.push(
1978
2358
  attrStringArray(
1979
2359
  "ai.prompt.tools",
1980
- event.stepTools.map((t) => JSON.stringify(t))
2360
+ event.stepTools.map((t) => {
2361
+ var _a;
2362
+ return (_a = safeJsonWithUint8(t)) != null ? _a : "";
2363
+ })
1981
2364
  )
1982
2365
  );
1983
2366
  }
@@ -1985,7 +2368,7 @@ var RaindropTelemetryIntegration = class {
1985
2368
  inputAttrs.push(
1986
2369
  attrString(
1987
2370
  "ai.prompt.toolChoice",
1988
- JSON.stringify(event.stepToolChoice)
2371
+ safeJsonWithUint8(event.stepToolChoice)
1989
2372
  )
1990
2373
  );
1991
2374
  }
@@ -2041,7 +2424,9 @@ var RaindropTelemetryIntegration = class {
2041
2424
  if (chunk.type === "text-delta") {
2042
2425
  const delta = (_c = chunk.textDelta) != null ? _c : chunk.delta;
2043
2426
  if (typeof delta === "string") {
2044
- state.accumulatedText += delta;
2427
+ if (state.accumulatedText.length <= effectiveMaxTextFieldChars()) {
2428
+ state.accumulatedText += delta;
2429
+ }
2045
2430
  this.emitLive(state, "text_delta", delta);
2046
2431
  }
2047
2432
  } else if (chunk.type === "reasoning" || chunk.type === "reasoning-delta") {
@@ -2060,7 +2445,7 @@ var RaindropTelemetryIntegration = class {
2060
2445
  if (state.recordOutputs) {
2061
2446
  outputAttrs.push(
2062
2447
  attrString("ai.response.finishReason", event.finishReason),
2063
- attrString("ai.response.text", (_a = event.text) != null ? _a : void 0),
2448
+ attrString("ai.response.text", capText2((_a = event.text) != null ? _a : void 0)),
2064
2449
  attrString("ai.response.id", (_b = event.response) == null ? void 0 : _b.id),
2065
2450
  attrString("ai.response.model", (_c = event.response) == null ? void 0 : _c.modelId),
2066
2451
  attrString(
@@ -2076,7 +2461,7 @@ var RaindropTelemetryIntegration = class {
2076
2461
  outputAttrs.push(
2077
2462
  attrString(
2078
2463
  "ai.response.toolCalls",
2079
- JSON.stringify(
2464
+ safeJsonWithUint8(
2080
2465
  event.toolCalls.map((tc) => ({
2081
2466
  toolCallId: tc.toolCallId,
2082
2467
  toolName: tc.toolName,
@@ -2089,7 +2474,7 @@ var RaindropTelemetryIntegration = class {
2089
2474
  if (((_g = event.reasoning) == null ? void 0 : _g.length) > 0) {
2090
2475
  const reasoningText = event.reasoning.filter((part) => "text" in part).map((part) => part.text).join("\n");
2091
2476
  if (reasoningText) {
2092
- outputAttrs.push(attrString("ai.response.reasoning", reasoningText));
2477
+ outputAttrs.push(attrString("ai.response.reasoning", capText2(reasoningText)));
2093
2478
  }
2094
2479
  }
2095
2480
  }
@@ -2511,7 +2896,7 @@ var RaindropTelemetryIntegration = class {
2511
2896
  if (state.recordOutputs) {
2512
2897
  outputAttrs.push(
2513
2898
  attrString("ai.response.finishReason", event.finishReason),
2514
- attrString("ai.response.text", (_a = event.text) != null ? _a : void 0),
2899
+ attrString("ai.response.text", capText2((_a = event.text) != null ? _a : void 0)),
2515
2900
  attrString(
2516
2901
  "ai.response.providerMetadata",
2517
2902
  event.providerMetadata ? safeJsonWithUint8(event.providerMetadata) : void 0
@@ -2521,7 +2906,7 @@ var RaindropTelemetryIntegration = class {
2521
2906
  outputAttrs.push(
2522
2907
  attrString(
2523
2908
  "ai.response.toolCalls",
2524
- JSON.stringify(
2909
+ safeJsonWithUint8(
2525
2910
  event.toolCalls.map((tc) => ({
2526
2911
  toolCallId: tc.toolCallId,
2527
2912
  toolName: tc.toolName,
@@ -2534,7 +2919,7 @@ var RaindropTelemetryIntegration = class {
2534
2919
  if (((_c = event.reasoning) == null ? void 0 : _c.length) > 0) {
2535
2920
  const reasoningText = event.reasoning.filter((part) => "text" in part).map((part) => part.text).join("\n");
2536
2921
  if (reasoningText) {
2537
- outputAttrs.push(attrString("ai.response.reasoning", reasoningText));
2922
+ outputAttrs.push(attrString("ai.response.reasoning", capText2(reasoningText)));
2538
2923
  }
2539
2924
  }
2540
2925
  }
@@ -2572,7 +2957,8 @@ var RaindropTelemetryIntegration = class {
2572
2957
  const userId = (_b = callMeta.userId) != null ? _b : (_a = this.defaultContext) == null ? void 0 : _a.userId;
2573
2958
  if (!userId) return;
2574
2959
  const eventName = (_e = (_d = callMeta.eventName) != null ? _d : (_c = this.defaultContext) == null ? void 0 : _c.eventName) != null ? _e : state.operationId;
2575
- const input = state.inputText;
2960
+ const cappedOutput = capText2(output);
2961
+ const input = capText2(state.inputText);
2576
2962
  const properties = {
2577
2963
  ...(_f = this.defaultContext) == null ? void 0 : _f.properties,
2578
2964
  ...callMeta.properties
@@ -2583,7 +2969,7 @@ var RaindropTelemetryIntegration = class {
2583
2969
  userId,
2584
2970
  convoId,
2585
2971
  input,
2586
- output,
2972
+ output: cappedOutput,
2587
2973
  model,
2588
2974
  properties: Object.keys(properties).length > 0 ? properties : void 0,
2589
2975
  isPending: false
@@ -3056,13 +3442,14 @@ function shouldKeepEventPending(params) {
3056
3442
  return params.finishReason === "tool-calls" || params.finishReason === "tool_calls";
3057
3443
  }
3058
3444
  function normalizePromptAttr(arg) {
3445
+ var _a, _b;
3059
3446
  if (!isRecord(arg)) return arg;
3060
3447
  const system = arg["system"];
3061
3448
  const prompt = arg["prompt"];
3062
3449
  const messages = arg["messages"];
3063
3450
  if (Array.isArray(messages)) {
3064
3451
  if (system) {
3065
- const sysContent = typeof system === "string" ? system : JSON.stringify(system);
3452
+ const sysContent = typeof system === "string" ? system : (_a = safeJsonWithUint8(system)) != null ? _a : String(system);
3066
3453
  return [{ role: "system", content: sysContent }, ...messages];
3067
3454
  }
3068
3455
  return messages;
@@ -3070,7 +3457,10 @@ function normalizePromptAttr(arg) {
3070
3457
  if (typeof prompt === "string") {
3071
3458
  const msgs = [];
3072
3459
  if (system) {
3073
- msgs.push({ role: "system", content: typeof system === "string" ? system : JSON.stringify(system) });
3460
+ msgs.push({
3461
+ role: "system",
3462
+ content: typeof system === "string" ? system : (_b = safeJsonWithUint8(system)) != null ? _b : String(system)
3463
+ });
3074
3464
  }
3075
3465
  msgs.push({ role: "user", content: prompt });
3076
3466
  return msgs;
@@ -3239,7 +3629,8 @@ function createFinalize(params) {
3239
3629
  };
3240
3630
  const built = await maybeBuildEvent(options.buildEvent, allMessages);
3241
3631
  const patch = mergeBuildEventPatch(defaultPatch, built);
3242
- const output = patch.output;
3632
+ const output = capText2(patch.output);
3633
+ const input = capText2(patch.input);
3243
3634
  const finalModel = (_c = patch.model) != null ? _c : model;
3244
3635
  if (setup.rootSpan) {
3245
3636
  const spanEndTimeUnixNano = nowUnixNanoString();
@@ -3283,7 +3674,7 @@ function createFinalize(params) {
3283
3674
  eventName: patch.eventName,
3284
3675
  userId: setup.ctx.userId,
3285
3676
  convoId: setup.ctx.convoId,
3286
- input: patch.input,
3677
+ input,
3287
3678
  output,
3288
3679
  model: finalModel,
3289
3680
  properties: patch.properties,
@@ -3798,7 +4189,8 @@ function wrapAgentGenerate(generate, instance, agentSettings, className, aiSDK,
3798
4189
  };
3799
4190
  const built = await maybeBuildEvent(deps.options.buildEvent, allMessages);
3800
4191
  const patch = mergeBuildEventPatch(defaultPatch, built);
3801
- const output = patch.output;
4192
+ const output = capText2(patch.output);
4193
+ const input = capText2(patch.input);
3802
4194
  const finalModel = (_c2 = patch.model) != null ? _c2 : model;
3803
4195
  if (rootSpan) {
3804
4196
  const spanEndTimeUnixNano = nowUnixNanoString();
@@ -3856,7 +4248,7 @@ function wrapAgentGenerate(generate, instance, agentSettings, className, aiSDK,
3856
4248
  eventName: patch.eventName,
3857
4249
  userId: ctx.userId,
3858
4250
  convoId: ctx.convoId,
3859
- input: patch.input,
4251
+ input,
3860
4252
  output,
3861
4253
  model: finalModel,
3862
4254
  properties: patch.properties,
@@ -4005,7 +4397,8 @@ function wrapAgentStream(stream, instance, agentSettings, className, aiSDK, deps
4005
4397
  };
4006
4398
  const built = await maybeBuildEvent(deps.options.buildEvent, allMessages);
4007
4399
  const patch = mergeBuildEventPatch(defaultPatch, built);
4008
- const output = patch.output;
4400
+ const output = capText2(patch.output);
4401
+ const input = capText2(patch.input);
4009
4402
  const finalModel = (_c2 = patch.model) != null ? _c2 : model;
4010
4403
  if (rootSpan) {
4011
4404
  const spanEndTimeUnixNano = nowUnixNanoString();
@@ -4063,7 +4456,7 @@ function wrapAgentStream(stream, instance, agentSettings, className, aiSDK, deps
4063
4456
  eventName: patch.eventName,
4064
4457
  userId: ctx.userId,
4065
4458
  convoId: ctx.convoId,
4066
- input: patch.input,
4459
+ input,
4067
4460
  output,
4068
4461
  model: finalModel,
4069
4462
  properties: patch.properties,
@@ -4403,7 +4796,7 @@ function wrapModel(args, aiSDK, outerOperationId, ctx) {
4403
4796
  attributes: [
4404
4797
  ...((_a = ctx.telemetry) == null ? void 0 : _a.recordOutputs) === false ? [] : [
4405
4798
  attrString("ai.response.finishReason", finishReason),
4406
- attrString("ai.response.text", activeText.length ? activeText : void 0),
4799
+ attrString("ai.response.text", activeText.length ? capText2(activeText) : void 0),
4407
4800
  attrString(
4408
4801
  "ai.response.toolCalls",
4409
4802
  safeJsonWithUint8(toolCallsLocal.length ? toolCallsLocal : void 0)
@@ -4456,7 +4849,9 @@ function wrapModel(args, aiSDK, outerOperationId, ctx) {
4456
4849
  } else if (typeof value["textDelta"] === "string") {
4457
4850
  textDelta = value["textDelta"];
4458
4851
  }
4459
- if (typeof textDelta === "string") activeText += textDelta;
4852
+ if (typeof textDelta === "string" && activeText.length <= effectiveMaxTextFieldChars()) {
4853
+ activeText += textDelta;
4854
+ }
4460
4855
  }
4461
4856
  if (type === "finish") finishReason = extractFinishReason(value);
4462
4857
  if (type === "tool-call") toolCallsLocal.push(value);
@@ -4729,8 +5124,8 @@ function eventMetadataFromChatRequest(options) {
4729
5124
  });
4730
5125
  }
4731
5126
  function stringify(v) {
4732
- if (typeof v === "string") return v;
4733
- return JSON.stringify(v);
5127
+ if (typeof v === "string") return capText2(v);
5128
+ return boundedStringify(v);
4734
5129
  }
4735
5130
  function userAttrsToOtlp(attrs) {
4736
5131
  if (!attrs) return [];
@@ -4748,6 +5143,7 @@ function createRaindropAISDK(opts) {
4748
5143
  const eventsRequested = ((_a = opts.events) == null ? void 0 : _a.enabled) !== false;
4749
5144
  const tracesRequested = ((_b = opts.traces) == null ? void 0 : _b.enabled) !== false;
4750
5145
  const envDebug = envDebugEnabled();
5146
+ setMaxTextFieldChars(opts.maxTextFieldChars);
4751
5147
  const localWorkshopInput = opts.localWorkshopUrl === false ? null : opts.localWorkshopUrl;
4752
5148
  const resolvedLocalDebuggerUrl = resolveLocalDebuggerBaseUrl(localWorkshopInput);
4753
5149
  const localDebuggerUrl = localWorkshopInput === null ? null : resolvedLocalDebuggerUrl != null ? resolvedLocalDebuggerUrl : void 0;
@@ -4765,6 +5161,7 @@ function createRaindropAISDK(opts) {
4765
5161
  enabled: eventsEnabled,
4766
5162
  debug: ((_c = opts.events) == null ? void 0 : _c.debug) === true || envDebug,
4767
5163
  partialFlushMs: (_d = opts.events) == null ? void 0 : _d.partialFlushMs,
5164
+ projectId: opts.projectId,
4768
5165
  localDebuggerUrl
4769
5166
  });
4770
5167
  const traceShipper = new TraceShipper2({
@@ -4776,6 +5173,7 @@ function createRaindropAISDK(opts) {
4776
5173
  flushIntervalMs: (_g = opts.traces) == null ? void 0 : _g.flushIntervalMs,
4777
5174
  maxBatchSize: (_h = opts.traces) == null ? void 0 : _h.maxBatchSize,
4778
5175
  maxQueueSize: (_i = opts.traces) == null ? void 0 : _i.maxQueueSize,
5176
+ projectId: opts.projectId,
4779
5177
  localDebuggerUrl,
4780
5178
  transformSpan: (_j = opts.traces) == null ? void 0 : _j.transformSpan,
4781
5179
  disableDefaultRedaction: (_k = opts.traces) == null ? void 0 : _k.disableDefaultRedaction
@@ -4937,13 +5335,17 @@ function raindrop(options = {}) {
4937
5335
  // src/index.node.ts
4938
5336
  globalThis.RAINDROP_ASYNC_LOCAL_STORAGE = async_hooks.AsyncLocalStorage;
4939
5337
 
5338
+ exports.DEFAULT_MAX_TEXT_FIELD_CHARS = DEFAULT_MAX_TEXT_FIELD_CHARS2;
4940
5339
  exports.DEFAULT_REDACT_ATTRIBUTE_KEYS = DEFAULT_REDACT_ATTRIBUTE_KEYS;
4941
5340
  exports.DEFAULT_SECRET_KEY_NAMES = DEFAULT_SECRET_KEY_NAMES;
4942
5341
  exports.REDACTED_PLACEHOLDER = REDACTED_PLACEHOLDER;
4943
5342
  exports.RaindropTelemetryIntegration = RaindropTelemetryIntegration;
5343
+ exports.TRUNCATION_MARKER = TRUNCATION_MARKER2;
4944
5344
  exports._resetParentToolContextStorage = _resetParentToolContextStorage;
4945
5345
  exports._resetRaindropCallMetadataStorage = _resetRaindropCallMetadataStorage;
4946
5346
  exports._resetWarnedMissingUserId = _resetWarnedMissingUserId;
5347
+ exports.boundedStringify = boundedStringify;
5348
+ exports.capText = capText2;
4947
5349
  exports.clearParentToolContext = clearParentToolContext;
4948
5350
  exports.createRaindropAISDK = createRaindropAISDK;
4949
5351
  exports.currentSpan = currentSpan;