@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.
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- // ../core/dist/chunk-VUNUOE2X.js
3
+ // ../core/dist/chunk-SK6EJEO7.js
4
4
  function getCrypto() {
5
5
  const c = globalThis.crypto;
6
6
  return c;
@@ -55,6 +55,8 @@ function base64Encode(bytes) {
55
55
  }
56
56
  return out;
57
57
  }
58
+ var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
59
+ var MAX_RETRY_DELAY_MS = 3e4;
58
60
  function wait(ms) {
59
61
  return new Promise((resolve) => setTimeout(resolve, ms));
60
62
  }
@@ -62,6 +64,46 @@ function formatEndpoint(endpoint) {
62
64
  if (!endpoint) return void 0;
63
65
  return endpoint.endsWith("/") ? endpoint : `${endpoint}/`;
64
66
  }
67
+ function redactUrlForLog(url) {
68
+ try {
69
+ const parsed = new URL(url);
70
+ parsed.username = "";
71
+ parsed.password = "";
72
+ parsed.search = "";
73
+ parsed.hash = "";
74
+ return parsed.toString();
75
+ } catch (e) {
76
+ return "<unparseable-url>";
77
+ }
78
+ }
79
+ var RATE_LIMITED_LOG_INTERVAL_MS = 3e4;
80
+ var rateLimitedLogLast = /* @__PURE__ */ new Map();
81
+ function rateLimitedLog(key, log) {
82
+ const now = Date.now();
83
+ const last = rateLimitedLogLast.get(key);
84
+ if (last !== void 0 && now - last < RATE_LIMITED_LOG_INTERVAL_MS) {
85
+ return false;
86
+ }
87
+ rateLimitedLogLast.set(key, now);
88
+ log();
89
+ return true;
90
+ }
91
+ async function raceWithTimeout(promise, timeoutMs) {
92
+ let timer;
93
+ const settledInTime = await Promise.race([
94
+ promise.then(
95
+ () => true,
96
+ () => true
97
+ ),
98
+ new Promise((resolve) => {
99
+ var _a;
100
+ timer = setTimeout(() => resolve(false), Math.max(0, timeoutMs));
101
+ (_a = timer.unref) == null ? void 0 : _a.call(timer);
102
+ })
103
+ ]);
104
+ if (timer) clearTimeout(timer);
105
+ return settledInTime;
106
+ }
65
107
  function parseRetryAfter(headers) {
66
108
  var _a;
67
109
  const value = (_a = headers.get("Retry-After")) != null ? _a : headers.get("retry-after");
@@ -78,12 +120,12 @@ function parseRetryAfter(headers) {
78
120
  function getRetryDelayMs(attemptNumber, previousError) {
79
121
  if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
80
122
  const v = previousError.retryAfterMs;
81
- if (typeof v === "number") return Math.max(0, v);
123
+ if (typeof v === "number") return Math.min(Math.max(0, v), MAX_RETRY_DELAY_MS);
82
124
  }
83
125
  if (attemptNumber <= 1) return 0;
84
126
  const base = 500;
85
127
  const factor = Math.pow(2, attemptNumber - 2);
86
- return base * factor;
128
+ return Math.min(base * factor, MAX_RETRY_DELAY_MS);
87
129
  }
88
130
  async function withRetry(operation, opName2, opts) {
89
131
  const prefix = opts.sdkName ? `[raindrop-ai/${opts.sdkName}]` : "[raindrop-ai/core]";
@@ -118,7 +160,9 @@ async function withRetry(operation, opName2, opts) {
118
160
  throw lastError instanceof Error ? lastError : new Error(String(lastError));
119
161
  }
120
162
  async function postJson(url, body, headers, opts) {
121
- const opName2 = `POST ${url}`;
163
+ var _a;
164
+ const opName2 = `POST ${redactUrlForLog(url)}`;
165
+ const timeoutMs = (_a = opts.timeoutMs) != null ? _a : DEFAULT_REQUEST_TIMEOUT_MS;
122
166
  await withRetry(
123
167
  async () => {
124
168
  const resp = await fetch(url, {
@@ -127,7 +171,8 @@ async function postJson(url, body, headers, opts) {
127
171
  "Content-Type": "application/json",
128
172
  ...headers
129
173
  },
130
- body: JSON.stringify(body)
174
+ body: JSON.stringify(body),
175
+ signal: AbortSignal.timeout(timeoutMs)
131
176
  });
132
177
  if (!resp.ok) {
133
178
  const text = await resp.text().catch(() => "");
@@ -144,6 +189,27 @@ async function postJson(url, body, headers, opts) {
144
189
  opts
145
190
  );
146
191
  }
192
+ var DEFAULT_MAX_TEXT_FIELD_CHARS = 1e6;
193
+ var TRUNCATION_MARKER = "...[truncated by raindrop]";
194
+ var currentDefaultMaxTextFieldChars = DEFAULT_MAX_TEXT_FIELD_CHARS;
195
+ function resolveMaxTextFieldChars(value) {
196
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
197
+ return Math.floor(value);
198
+ }
199
+ return currentDefaultMaxTextFieldChars;
200
+ }
201
+ function truncateToLimit(text, limit) {
202
+ if (limit > TRUNCATION_MARKER.length) {
203
+ return text.slice(0, limit - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;
204
+ }
205
+ return text.slice(0, Math.max(0, limit));
206
+ }
207
+ function capText(value, limit) {
208
+ if (typeof value !== "string") return value;
209
+ const max = limit != null ? limit : currentDefaultMaxTextFieldChars;
210
+ if (value.length <= max) return value;
211
+ return truncateToLimit(value, max);
212
+ }
147
213
  var SpanStatusCode = {
148
214
  UNSET: 0,
149
215
  ERROR: 2
@@ -331,6 +397,27 @@ function sendLocalDebuggerLiveEvent(event, options = {}) {
331
397
  ).catch(() => {
332
398
  });
333
399
  }
400
+ var PROJECT_ID_HEADER = "X-Raindrop-Project-Id";
401
+ var PROJECT_ID_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
402
+ function isValidProjectIdSlug(value) {
403
+ return PROJECT_ID_SLUG_PATTERN.test(value);
404
+ }
405
+ function normalizeProjectId(raw, opts) {
406
+ if (typeof raw !== "string") return void 0;
407
+ const trimmed = raw.trim();
408
+ if (!trimmed) return void 0;
409
+ if (!isValidProjectIdSlug(trimmed) && opts.debug) {
410
+ console.warn(
411
+ `${opts.prefix} projectId "${trimmed}" does not match slug ${PROJECT_ID_SLUG_PATTERN.source}; sending anyway \u2014 backend may reject with HTTP 400`
412
+ );
413
+ }
414
+ return trimmed;
415
+ }
416
+ function projectIdHeaders(projectId) {
417
+ return projectId ? { [PROJECT_ID_HEADER]: projectId } : {};
418
+ }
419
+ var SHUTDOWN_DEADLINE_MS = 1e4;
420
+ var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
334
421
  function mergePatches(target, source) {
335
422
  var _a, _b, _c, _d;
336
423
  const out = { ...target, ...source };
@@ -348,6 +435,7 @@ var EventShipper = class {
348
435
  this.sticky = /* @__PURE__ */ new Map();
349
436
  this.timers = /* @__PURE__ */ new Map();
350
437
  this.inFlight = /* @__PURE__ */ new Set();
438
+ this.hasShutdown = false;
351
439
  var _a, _b, _c, _d, _e, _f, _g, _h;
352
440
  this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
353
441
  this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
@@ -357,10 +445,15 @@ var EventShipper = class {
357
445
  this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
358
446
  this.prefix = `[raindrop-ai/${this.sdkName}]`;
359
447
  this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
448
+ this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
360
449
  this.localDebuggerUrl = (_f = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _f : void 0;
361
450
  if (this.debug && this.localDebuggerUrl) {
362
451
  console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
363
452
  }
453
+ this.projectId = normalizeProjectId(opts.projectId, {
454
+ debug: this.debug,
455
+ prefix: this.prefix
456
+ });
364
457
  const isNode = typeof process !== "undefined" && typeof process.version === "string";
365
458
  this.context = {
366
459
  library: {
@@ -379,10 +472,55 @@ var EventShipper = class {
379
472
  authHeaders() {
380
473
  return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
381
474
  }
475
+ requestHeaders() {
476
+ return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
477
+ }
478
+ /**
479
+ * Build the retry/timeout options for one POST, honoring the shutdown
480
+ * deadline. Returns `null` when the shutdown drain window is exhausted —
481
+ * the caller must drop the payload (with a rate-limited warning) instead
482
+ * of issuing a request that could outlive process exit.
483
+ *
484
+ * Checked fresh on EVERY send, so a shutdown that begins while the flush
485
+ * path is mid-drain takes effect immediately: no further retries, and the
486
+ * per-attempt timeout is clamped to the remaining window. After
487
+ * `shutdown()` returns (deadline cleared, `hasShutdown` still set),
488
+ * sends — late callers, or flush work the deadline abandoned mid-drain —
489
+ * run as a single short attempt rather than regaining the full retry
490
+ * schedule.
491
+ */
492
+ requestOpts() {
493
+ if (this.shutdownDeadlineAt !== void 0) {
494
+ const remainingMs = this.shutdownDeadlineAt - Date.now();
495
+ if (remainingMs <= 0) return null;
496
+ return {
497
+ maxAttempts: 1,
498
+ debug: this.debug,
499
+ sdkName: this.sdkName,
500
+ timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
501
+ };
502
+ }
503
+ if (this.hasShutdown) {
504
+ return {
505
+ maxAttempts: 1,
506
+ debug: this.debug,
507
+ sdkName: this.sdkName,
508
+ timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
509
+ };
510
+ }
511
+ return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
512
+ }
382
513
  async patch(eventId, patch) {
383
514
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
384
515
  if (!this.enabled) return;
385
516
  if (!eventId || !eventId.trim()) return;
517
+ const maxChars = resolveMaxTextFieldChars(this.maxTextFieldCharsOpt);
518
+ if (typeof patch.input === "string" && patch.input.length > maxChars) {
519
+ patch = { ...patch, input: capText(patch.input, maxChars) };
520
+ }
521
+ if (typeof patch.output === "string" && patch.output.length > maxChars) {
522
+ patch = { ...patch, output: capText(patch.output, maxChars) };
523
+ }
386
524
  if (this.debug) {
387
525
  console.log(`${this.prefix} queue patch`, {
388
526
  eventId,
@@ -429,9 +567,16 @@ var EventShipper = class {
429
567
  })));
430
568
  }
431
569
  async shutdown() {
432
- for (const t of this.timers.values()) clearTimeout(t);
433
- this.timers.clear();
434
- await this.flush();
570
+ this.hasShutdown = true;
571
+ this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
572
+ try {
573
+ for (const t of this.timers.values()) clearTimeout(t);
574
+ this.timers.clear();
575
+ const settled = await raceWithTimeout(this.flush(), SHUTDOWN_DEADLINE_MS);
576
+ if (!settled) this.warnShutdownDrop("in-flight request(s) at shutdown");
577
+ } finally {
578
+ this.shutdownDeadlineAt = void 0;
579
+ }
435
580
  }
436
581
  async trackSignal(signal) {
437
582
  var _a, _b;
@@ -453,15 +598,19 @@ var EventShipper = class {
453
598
  ];
454
599
  if (!this.writeKey) return;
455
600
  const url = `${this.baseUrl}signals/track`;
601
+ const opts = this.requestOpts();
602
+ if (!opts) {
603
+ this.warnShutdownDrop("signal");
604
+ return;
605
+ }
456
606
  try {
457
- await postJson(url, body, this.authHeaders(), {
458
- maxAttempts: 3,
459
- debug: this.debug,
460
- sdkName: this.sdkName
461
- });
607
+ await postJson(url, body, this.requestHeaders(), opts);
462
608
  } catch (err) {
463
609
  const msg = err instanceof Error ? err.message : String(err);
464
- console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`);
610
+ rateLimitedLog(
611
+ `${this.prefix}.send_signal_failed`,
612
+ () => console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`)
613
+ );
465
614
  }
466
615
  }
467
616
  async identify(users) {
@@ -485,17 +634,27 @@ var EventShipper = class {
485
634
  if (!this.writeKey) return;
486
635
  if (body.length === 0) return;
487
636
  const url = `${this.baseUrl}users/identify`;
637
+ const opts = this.requestOpts();
638
+ if (!opts) {
639
+ this.warnShutdownDrop("identify");
640
+ return;
641
+ }
488
642
  try {
489
- await postJson(url, body, this.authHeaders(), {
490
- maxAttempts: 3,
491
- debug: this.debug,
492
- sdkName: this.sdkName
493
- });
643
+ await postJson(url, body, this.requestHeaders(), opts);
494
644
  } catch (err) {
495
645
  const msg = err instanceof Error ? err.message : String(err);
496
- console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`);
646
+ rateLimitedLog(
647
+ `${this.prefix}.send_identify_failed`,
648
+ () => console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`)
649
+ );
497
650
  }
498
651
  }
652
+ warnShutdownDrop(what) {
653
+ rateLimitedLog(
654
+ `${this.prefix}.shutdown_deadline`,
655
+ () => console.warn(`${this.prefix} shutdown flush deadline exceeded; dropping ${what}`)
656
+ );
657
+ }
499
658
  async flushOne(eventId) {
500
659
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
501
660
  if (!this.enabled) return;
@@ -571,11 +730,13 @@ var EventShipper = class {
571
730
  if (!isPending) this.sticky.delete(eventId);
572
731
  return;
573
732
  }
574
- const p = postJson(url, payload, this.authHeaders(), {
575
- maxAttempts: 3,
576
- debug: this.debug,
577
- sdkName: this.sdkName
578
- });
733
+ const opts = this.requestOpts();
734
+ if (!opts) {
735
+ this.warnShutdownDrop(`track_partial ${eventId}`);
736
+ if (!isPending) this.sticky.delete(eventId);
737
+ return;
738
+ }
739
+ const p = postJson(url, payload, this.requestHeaders(), opts);
579
740
  this.inFlight.add(p);
580
741
  try {
581
742
  try {
@@ -585,7 +746,10 @@ var EventShipper = class {
585
746
  }
586
747
  } catch (err) {
587
748
  const msg = err instanceof Error ? err.message : String(err);
588
- console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`);
749
+ rateLimitedLog(
750
+ `${this.prefix}.send_track_partial_failed`,
751
+ () => console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`)
752
+ );
589
753
  }
590
754
  } finally {
591
755
  this.inFlight.delete(p);
@@ -684,10 +848,24 @@ function redactJsonAttributeValue(key, value) {
684
848
  if (scrubbedJson === json) return void 0;
685
849
  return { stringValue: scrubbedJson };
686
850
  }
851
+ function applyOtelSpanAttributeLimit(limit) {
852
+ var _a, _b;
853
+ try {
854
+ 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;
855
+ if (!raw) return limit;
856
+ const parsed = Number.parseInt(raw, 10);
857
+ if (Number.isFinite(parsed) && parsed > 0) {
858
+ return Math.min(limit, parsed);
859
+ }
860
+ } catch (e) {
861
+ }
862
+ return limit;
863
+ }
687
864
  var TraceShipper = class {
688
865
  constructor(opts) {
689
866
  this.queue = [];
690
867
  this.inFlight = /* @__PURE__ */ new Set();
868
+ this.hasShutdown = false;
691
869
  var _a, _b, _c, _d, _e, _f, _g, _h, _i;
692
870
  this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
693
871
  this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
@@ -705,8 +883,45 @@ var TraceShipper = class {
705
883
  if (this.debug && this.localDebuggerUrl) {
706
884
  console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
707
885
  }
886
+ this.projectId = normalizeProjectId(opts.projectId, {
887
+ debug: this.debug,
888
+ prefix: this.prefix
889
+ });
708
890
  this.transformSpanHook = opts.transformSpan;
709
891
  this.disableDefaultRedaction = opts.disableDefaultRedaction === true;
892
+ this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
893
+ }
894
+ /**
895
+ * Cap every string attribute value on the span. O(#attributes) length
896
+ * checks; only oversized values pay a slice. Runs AFTER the redaction
897
+ * pipeline so the default secret-scrub still sees parseable JSON in
898
+ * `ai.request.providerOptions` / `ai.response.providerMetadata` (capping
899
+ * first could cut a JSON blob mid-way, fail the parse, and ship secrets
900
+ * in the surviving prefix).
901
+ *
902
+ * A stricter `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var is honored
903
+ * for span content, matching the Python SDK and the OTel SDK convention.
904
+ */
905
+ capSpanAttributes(span) {
906
+ var _a;
907
+ const maxChars = applyOtelSpanAttributeLimit(
908
+ resolveMaxTextFieldChars(this.maxTextFieldCharsOpt)
909
+ );
910
+ const attrs = span.attributes;
911
+ if (!attrs || attrs.length === 0) return span;
912
+ let nextAttrs;
913
+ for (let i = 0; i < attrs.length; i++) {
914
+ const attr = attrs[i];
915
+ const value = (_a = attr.value) == null ? void 0 : _a.stringValue;
916
+ if (typeof value !== "string" || value.length <= maxChars) continue;
917
+ if (!nextAttrs) nextAttrs = attrs.slice();
918
+ nextAttrs[i] = {
919
+ key: attr.key,
920
+ value: { ...attr.value, stringValue: capText(value, maxChars) }
921
+ };
922
+ }
923
+ if (!nextAttrs) return span;
924
+ return { ...span, attributes: nextAttrs };
710
925
  }
711
926
  /**
712
927
  * Apply the user `transformSpan` hook (if any) followed by the default
@@ -740,7 +955,7 @@ var TraceShipper = class {
740
955
  if (!this.disableDefaultRedaction) {
741
956
  current = defaultTransformSpan(current);
742
957
  }
743
- return current;
958
+ return this.capSpanAttributes(current);
744
959
  }
745
960
  isDebugEnabled() {
746
961
  return this.debug;
@@ -748,6 +963,9 @@ var TraceShipper = class {
748
963
  authHeaders() {
749
964
  return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
750
965
  }
966
+ requestHeaders() {
967
+ return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
968
+ }
751
969
  startSpan(args) {
752
970
  var _a, _b;
753
971
  const ids = createSpanIds(args.parent);
@@ -861,6 +1079,16 @@ var TraceShipper = class {
861
1079
  while (this.queue.length > 0) {
862
1080
  const batch = this.queue.splice(0, this.maxBatchSize);
863
1081
  if (!this.writeKey) continue;
1082
+ const opts = this.requestOpts();
1083
+ if (!opts) {
1084
+ rateLimitedLog(
1085
+ `${this.prefix}.shutdown_deadline`,
1086
+ () => console.warn(
1087
+ `${this.prefix} shutdown flush deadline exceeded; dropping ${batch.length} spans`
1088
+ )
1089
+ );
1090
+ continue;
1091
+ }
864
1092
  const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
865
1093
  const url = `${this.baseUrl}traces`;
866
1094
  if (this.debug) {
@@ -869,11 +1097,7 @@ var TraceShipper = class {
869
1097
  endpoint: url
870
1098
  });
871
1099
  }
872
- const p = postJson(url, body, this.authHeaders(), {
873
- maxAttempts: 3,
874
- debug: this.debug,
875
- sdkName: this.sdkName
876
- });
1100
+ const p = postJson(url, body, this.requestHeaders(), opts);
877
1101
  this.inFlight.add(p);
878
1102
  try {
879
1103
  try {
@@ -881,21 +1105,61 @@ var TraceShipper = class {
881
1105
  if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
882
1106
  } catch (err) {
883
1107
  const msg = err instanceof Error ? err.message : String(err);
884
- console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`);
1108
+ rateLimitedLog(
1109
+ `${this.prefix}.send_spans_failed`,
1110
+ () => console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`)
1111
+ );
885
1112
  }
886
1113
  } finally {
887
1114
  this.inFlight.delete(p);
888
1115
  }
889
1116
  }
890
1117
  }
1118
+ /** See EventShipper.requestOpts — same shutdown-budget semantics. */
1119
+ requestOpts() {
1120
+ if (this.shutdownDeadlineAt !== void 0) {
1121
+ const remainingMs = this.shutdownDeadlineAt - Date.now();
1122
+ if (remainingMs <= 0) return null;
1123
+ return {
1124
+ maxAttempts: 1,
1125
+ debug: this.debug,
1126
+ sdkName: this.sdkName,
1127
+ timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
1128
+ };
1129
+ }
1130
+ if (this.hasShutdown) {
1131
+ return {
1132
+ maxAttempts: 1,
1133
+ debug: this.debug,
1134
+ sdkName: this.sdkName,
1135
+ timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
1136
+ };
1137
+ }
1138
+ return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
1139
+ }
891
1140
  async shutdown() {
892
- if (this.timer) {
893
- clearTimeout(this.timer);
894
- this.timer = void 0;
1141
+ this.hasShutdown = true;
1142
+ this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
1143
+ try {
1144
+ if (this.timer) {
1145
+ clearTimeout(this.timer);
1146
+ this.timer = void 0;
1147
+ }
1148
+ const drain = async () => {
1149
+ await this.flush();
1150
+ await Promise.all([...this.inFlight].map((p) => p.catch(() => {
1151
+ })));
1152
+ };
1153
+ const settled = await raceWithTimeout(drain(), SHUTDOWN_DEADLINE_MS);
1154
+ if (!settled) {
1155
+ rateLimitedLog(
1156
+ `${this.prefix}.shutdown_deadline`,
1157
+ () => console.warn(`${this.prefix} shutdown flush deadline exceeded; abandoning in-flight spans`)
1158
+ );
1159
+ }
1160
+ } finally {
1161
+ this.shutdownDeadlineAt = void 0;
895
1162
  }
896
- await this.flush();
897
- await Promise.all([...this.inFlight].map((p) => p.catch(() => {
898
- })));
899
1163
  }
900
1164
  };
901
1165
  var NOOP_SPAN = {
@@ -1019,7 +1283,7 @@ async function* asyncGeneratorWithCurrent(span, gen) {
1019
1283
  // package.json
1020
1284
  var package_default = {
1021
1285
  name: "@raindrop-ai/ai-sdk",
1022
- version: "0.0.33"};
1286
+ version: "0.0.35"};
1023
1287
 
1024
1288
  // src/internal/version.ts
1025
1289
  var libraryName = package_default.name;
@@ -1038,6 +1302,133 @@ var EventShipper2 = class extends EventShipper {
1038
1302
  }
1039
1303
  };
1040
1304
 
1305
+ // src/internal/truncation.ts
1306
+ var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
1307
+ var DEFAULT_MAX_TEXT_FIELD_CHARS2 = 1e6;
1308
+ var MAX_DEPTH = 12;
1309
+ var configuredMaxTextFieldChars;
1310
+ function setMaxTextFieldChars(limit) {
1311
+ if (limit === void 0) return;
1312
+ if (typeof limit === "number" && Number.isFinite(limit) && limit > 0) {
1313
+ configuredMaxTextFieldChars = Math.floor(limit);
1314
+ } else {
1315
+ console.warn(`[raindrop-ai] maxTextFieldChars=${String(limit)} ignored; must be > 0`);
1316
+ }
1317
+ }
1318
+ function otelEnvLimit() {
1319
+ var _a;
1320
+ if (typeof process === "undefined") return void 0;
1321
+ const raw = (_a = process.env) == null ? void 0 : _a.OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT;
1322
+ if (!raw) return void 0;
1323
+ const parsed = Number.parseInt(raw, 10);
1324
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
1325
+ }
1326
+ function effectiveMaxTextFieldChars() {
1327
+ const base = configuredMaxTextFieldChars != null ? configuredMaxTextFieldChars : DEFAULT_MAX_TEXT_FIELD_CHARS2;
1328
+ const env = otelEnvLimit();
1329
+ return env !== void 0 && env < base ? env : base;
1330
+ }
1331
+ function truncateToLimit2(text, limit) {
1332
+ if (limit > TRUNCATION_MARKER2.length) {
1333
+ return text.slice(0, limit - TRUNCATION_MARKER2.length) + TRUNCATION_MARKER2;
1334
+ }
1335
+ return text.slice(0, limit);
1336
+ }
1337
+ function capText2(value, limit) {
1338
+ if (typeof value !== "string") return value;
1339
+ const max = limit != null ? limit : effectiveMaxTextFieldChars();
1340
+ if (value.length <= max) return value;
1341
+ return truncateToLimit2(value, max);
1342
+ }
1343
+ function boundedClone2(value, budget, depth) {
1344
+ var _a, _b;
1345
+ if (budget.remaining <= 0) return TRUNCATION_MARKER2;
1346
+ if (typeof value === "string") {
1347
+ if (value.length > budget.remaining) {
1348
+ const taken = value.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
1349
+ budget.remaining = 0;
1350
+ return taken;
1351
+ }
1352
+ budget.remaining -= Math.max(value.length, 1);
1353
+ return value;
1354
+ }
1355
+ if (value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
1356
+ budget.remaining -= 8;
1357
+ return value;
1358
+ }
1359
+ if (typeof value !== "object") {
1360
+ budget.remaining -= 1;
1361
+ return value;
1362
+ }
1363
+ if (value instanceof Uint8Array) {
1364
+ const maxBytes = Math.max(0, Math.ceil(budget.remaining * 3 / 4));
1365
+ if (value.byteLength > maxBytes) {
1366
+ const taken = base64Encode(value.subarray(0, maxBytes)) + TRUNCATION_MARKER2;
1367
+ budget.remaining = 0;
1368
+ return taken;
1369
+ }
1370
+ const encoded = base64Encode(value);
1371
+ budget.remaining -= Math.max(encoded.length, 1);
1372
+ return encoded;
1373
+ }
1374
+ if (depth >= MAX_DEPTH) {
1375
+ budget.remaining -= 16;
1376
+ const name = (_b = (_a = value.constructor) == null ? void 0 : _a.name) != null ? _b : "object";
1377
+ return `<max depth: ${name}>`;
1378
+ }
1379
+ const toJSON = value.toJSON;
1380
+ if (typeof toJSON === "function") {
1381
+ budget.remaining -= 2;
1382
+ return boundedClone2(toJSON.call(value), budget, depth + 1);
1383
+ }
1384
+ if (Array.isArray(value)) {
1385
+ budget.remaining -= 2;
1386
+ const out2 = [];
1387
+ for (const item of value) {
1388
+ if (budget.remaining <= 0) {
1389
+ out2.push(TRUNCATION_MARKER2);
1390
+ break;
1391
+ }
1392
+ out2.push(boundedClone2(item, budget, depth + 1));
1393
+ }
1394
+ return out2;
1395
+ }
1396
+ budget.remaining -= 2;
1397
+ const out = {};
1398
+ for (const key in value) {
1399
+ if (!Object.prototype.hasOwnProperty.call(value, key)) continue;
1400
+ if (budget.remaining <= 0) {
1401
+ out["..."] = TRUNCATION_MARKER2;
1402
+ break;
1403
+ }
1404
+ let cappedKey = key;
1405
+ if (key.length > budget.remaining) {
1406
+ cappedKey = key.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
1407
+ budget.remaining = 0;
1408
+ out[cappedKey] = TRUNCATION_MARKER2;
1409
+ break;
1410
+ }
1411
+ budget.remaining -= Math.max(key.length, 1);
1412
+ out[cappedKey] = boundedClone2(value[key], budget, depth + 1);
1413
+ }
1414
+ return out;
1415
+ }
1416
+ function boundedStringify(value, limit) {
1417
+ const max = limit != null ? limit : effectiveMaxTextFieldChars();
1418
+ try {
1419
+ if (typeof value === "string") {
1420
+ const text2 = JSON.stringify(capText2(value, max));
1421
+ return text2.length <= max ? text2 : truncateToLimit2(text2, max);
1422
+ }
1423
+ const pruned = boundedClone2(value, { remaining: max + TRUNCATION_MARKER2.length + 256 }, 0);
1424
+ const text = JSON.stringify(pruned);
1425
+ if (text === void 0) return void 0;
1426
+ return text.length <= max ? text : truncateToLimit2(text, max);
1427
+ } catch (e) {
1428
+ return void 0;
1429
+ }
1430
+ }
1431
+
1041
1432
  // src/internal/wrap/helpers.ts
1042
1433
  function isRecord(value) {
1043
1434
  return typeof value === "object" && value !== null;
@@ -1062,21 +1453,10 @@ function isModuleNamespace(obj) {
1062
1453
  }
1063
1454
  }
1064
1455
  function safeJson(value) {
1065
- try {
1066
- return JSON.stringify(value);
1067
- } catch (e) {
1068
- return void 0;
1069
- }
1456
+ return boundedStringify(value);
1070
1457
  }
1071
1458
  function safeJsonWithUint8(value) {
1072
- try {
1073
- return JSON.stringify(value, (_key, v) => {
1074
- if (v instanceof Uint8Array) return base64Encode(v);
1075
- return v;
1076
- });
1077
- } catch (e) {
1078
- return void 0;
1079
- }
1459
+ return boundedStringify(value);
1080
1460
  }
1081
1461
  function extractModelInfo(model) {
1082
1462
  if (typeof model === "string") {
@@ -1972,7 +2352,10 @@ var RaindropTelemetryIntegration = class {
1972
2352
  inputAttrs.push(
1973
2353
  attrStringArray(
1974
2354
  "ai.prompt.tools",
1975
- event.stepTools.map((t) => JSON.stringify(t))
2355
+ event.stepTools.map((t) => {
2356
+ var _a;
2357
+ return (_a = safeJsonWithUint8(t)) != null ? _a : "";
2358
+ })
1976
2359
  )
1977
2360
  );
1978
2361
  }
@@ -1980,7 +2363,7 @@ var RaindropTelemetryIntegration = class {
1980
2363
  inputAttrs.push(
1981
2364
  attrString(
1982
2365
  "ai.prompt.toolChoice",
1983
- JSON.stringify(event.stepToolChoice)
2366
+ safeJsonWithUint8(event.stepToolChoice)
1984
2367
  )
1985
2368
  );
1986
2369
  }
@@ -2036,7 +2419,9 @@ var RaindropTelemetryIntegration = class {
2036
2419
  if (chunk.type === "text-delta") {
2037
2420
  const delta = (_c = chunk.textDelta) != null ? _c : chunk.delta;
2038
2421
  if (typeof delta === "string") {
2039
- state.accumulatedText += delta;
2422
+ if (state.accumulatedText.length <= effectiveMaxTextFieldChars()) {
2423
+ state.accumulatedText += delta;
2424
+ }
2040
2425
  this.emitLive(state, "text_delta", delta);
2041
2426
  }
2042
2427
  } else if (chunk.type === "reasoning" || chunk.type === "reasoning-delta") {
@@ -2055,7 +2440,7 @@ var RaindropTelemetryIntegration = class {
2055
2440
  if (state.recordOutputs) {
2056
2441
  outputAttrs.push(
2057
2442
  attrString("ai.response.finishReason", event.finishReason),
2058
- attrString("ai.response.text", (_a = event.text) != null ? _a : void 0),
2443
+ attrString("ai.response.text", capText2((_a = event.text) != null ? _a : void 0)),
2059
2444
  attrString("ai.response.id", (_b = event.response) == null ? void 0 : _b.id),
2060
2445
  attrString("ai.response.model", (_c = event.response) == null ? void 0 : _c.modelId),
2061
2446
  attrString(
@@ -2071,7 +2456,7 @@ var RaindropTelemetryIntegration = class {
2071
2456
  outputAttrs.push(
2072
2457
  attrString(
2073
2458
  "ai.response.toolCalls",
2074
- JSON.stringify(
2459
+ safeJsonWithUint8(
2075
2460
  event.toolCalls.map((tc) => ({
2076
2461
  toolCallId: tc.toolCallId,
2077
2462
  toolName: tc.toolName,
@@ -2084,7 +2469,7 @@ var RaindropTelemetryIntegration = class {
2084
2469
  if (((_g = event.reasoning) == null ? void 0 : _g.length) > 0) {
2085
2470
  const reasoningText = event.reasoning.filter((part) => "text" in part).map((part) => part.text).join("\n");
2086
2471
  if (reasoningText) {
2087
- outputAttrs.push(attrString("ai.response.reasoning", reasoningText));
2472
+ outputAttrs.push(attrString("ai.response.reasoning", capText2(reasoningText)));
2088
2473
  }
2089
2474
  }
2090
2475
  }
@@ -2506,7 +2891,7 @@ var RaindropTelemetryIntegration = class {
2506
2891
  if (state.recordOutputs) {
2507
2892
  outputAttrs.push(
2508
2893
  attrString("ai.response.finishReason", event.finishReason),
2509
- attrString("ai.response.text", (_a = event.text) != null ? _a : void 0),
2894
+ attrString("ai.response.text", capText2((_a = event.text) != null ? _a : void 0)),
2510
2895
  attrString(
2511
2896
  "ai.response.providerMetadata",
2512
2897
  event.providerMetadata ? safeJsonWithUint8(event.providerMetadata) : void 0
@@ -2516,7 +2901,7 @@ var RaindropTelemetryIntegration = class {
2516
2901
  outputAttrs.push(
2517
2902
  attrString(
2518
2903
  "ai.response.toolCalls",
2519
- JSON.stringify(
2904
+ safeJsonWithUint8(
2520
2905
  event.toolCalls.map((tc) => ({
2521
2906
  toolCallId: tc.toolCallId,
2522
2907
  toolName: tc.toolName,
@@ -2529,7 +2914,7 @@ var RaindropTelemetryIntegration = class {
2529
2914
  if (((_c = event.reasoning) == null ? void 0 : _c.length) > 0) {
2530
2915
  const reasoningText = event.reasoning.filter((part) => "text" in part).map((part) => part.text).join("\n");
2531
2916
  if (reasoningText) {
2532
- outputAttrs.push(attrString("ai.response.reasoning", reasoningText));
2917
+ outputAttrs.push(attrString("ai.response.reasoning", capText2(reasoningText)));
2533
2918
  }
2534
2919
  }
2535
2920
  }
@@ -2567,7 +2952,8 @@ var RaindropTelemetryIntegration = class {
2567
2952
  const userId = (_b = callMeta.userId) != null ? _b : (_a = this.defaultContext) == null ? void 0 : _a.userId;
2568
2953
  if (!userId) return;
2569
2954
  const eventName = (_e = (_d = callMeta.eventName) != null ? _d : (_c = this.defaultContext) == null ? void 0 : _c.eventName) != null ? _e : state.operationId;
2570
- const input = state.inputText;
2955
+ const cappedOutput = capText2(output);
2956
+ const input = capText2(state.inputText);
2571
2957
  const properties = {
2572
2958
  ...(_f = this.defaultContext) == null ? void 0 : _f.properties,
2573
2959
  ...callMeta.properties
@@ -2578,7 +2964,7 @@ var RaindropTelemetryIntegration = class {
2578
2964
  userId,
2579
2965
  convoId,
2580
2966
  input,
2581
- output,
2967
+ output: cappedOutput,
2582
2968
  model,
2583
2969
  properties: Object.keys(properties).length > 0 ? properties : void 0,
2584
2970
  isPending: false
@@ -3051,13 +3437,14 @@ function shouldKeepEventPending(params) {
3051
3437
  return params.finishReason === "tool-calls" || params.finishReason === "tool_calls";
3052
3438
  }
3053
3439
  function normalizePromptAttr(arg) {
3440
+ var _a, _b;
3054
3441
  if (!isRecord(arg)) return arg;
3055
3442
  const system = arg["system"];
3056
3443
  const prompt = arg["prompt"];
3057
3444
  const messages = arg["messages"];
3058
3445
  if (Array.isArray(messages)) {
3059
3446
  if (system) {
3060
- const sysContent = typeof system === "string" ? system : JSON.stringify(system);
3447
+ const sysContent = typeof system === "string" ? system : (_a = safeJsonWithUint8(system)) != null ? _a : String(system);
3061
3448
  return [{ role: "system", content: sysContent }, ...messages];
3062
3449
  }
3063
3450
  return messages;
@@ -3065,7 +3452,10 @@ function normalizePromptAttr(arg) {
3065
3452
  if (typeof prompt === "string") {
3066
3453
  const msgs = [];
3067
3454
  if (system) {
3068
- msgs.push({ role: "system", content: typeof system === "string" ? system : JSON.stringify(system) });
3455
+ msgs.push({
3456
+ role: "system",
3457
+ content: typeof system === "string" ? system : (_b = safeJsonWithUint8(system)) != null ? _b : String(system)
3458
+ });
3069
3459
  }
3070
3460
  msgs.push({ role: "user", content: prompt });
3071
3461
  return msgs;
@@ -3234,7 +3624,8 @@ function createFinalize(params) {
3234
3624
  };
3235
3625
  const built = await maybeBuildEvent(options.buildEvent, allMessages);
3236
3626
  const patch = mergeBuildEventPatch(defaultPatch, built);
3237
- const output = patch.output;
3627
+ const output = capText2(patch.output);
3628
+ const input = capText2(patch.input);
3238
3629
  const finalModel = (_c = patch.model) != null ? _c : model;
3239
3630
  if (setup.rootSpan) {
3240
3631
  const spanEndTimeUnixNano = nowUnixNanoString();
@@ -3278,7 +3669,7 @@ function createFinalize(params) {
3278
3669
  eventName: patch.eventName,
3279
3670
  userId: setup.ctx.userId,
3280
3671
  convoId: setup.ctx.convoId,
3281
- input: patch.input,
3672
+ input,
3282
3673
  output,
3283
3674
  model: finalModel,
3284
3675
  properties: patch.properties,
@@ -3793,7 +4184,8 @@ function wrapAgentGenerate(generate, instance, agentSettings, className, aiSDK,
3793
4184
  };
3794
4185
  const built = await maybeBuildEvent(deps.options.buildEvent, allMessages);
3795
4186
  const patch = mergeBuildEventPatch(defaultPatch, built);
3796
- const output = patch.output;
4187
+ const output = capText2(patch.output);
4188
+ const input = capText2(patch.input);
3797
4189
  const finalModel = (_c2 = patch.model) != null ? _c2 : model;
3798
4190
  if (rootSpan) {
3799
4191
  const spanEndTimeUnixNano = nowUnixNanoString();
@@ -3851,7 +4243,7 @@ function wrapAgentGenerate(generate, instance, agentSettings, className, aiSDK,
3851
4243
  eventName: patch.eventName,
3852
4244
  userId: ctx.userId,
3853
4245
  convoId: ctx.convoId,
3854
- input: patch.input,
4246
+ input,
3855
4247
  output,
3856
4248
  model: finalModel,
3857
4249
  properties: patch.properties,
@@ -4000,7 +4392,8 @@ function wrapAgentStream(stream, instance, agentSettings, className, aiSDK, deps
4000
4392
  };
4001
4393
  const built = await maybeBuildEvent(deps.options.buildEvent, allMessages);
4002
4394
  const patch = mergeBuildEventPatch(defaultPatch, built);
4003
- const output = patch.output;
4395
+ const output = capText2(patch.output);
4396
+ const input = capText2(patch.input);
4004
4397
  const finalModel = (_c2 = patch.model) != null ? _c2 : model;
4005
4398
  if (rootSpan) {
4006
4399
  const spanEndTimeUnixNano = nowUnixNanoString();
@@ -4058,7 +4451,7 @@ function wrapAgentStream(stream, instance, agentSettings, className, aiSDK, deps
4058
4451
  eventName: patch.eventName,
4059
4452
  userId: ctx.userId,
4060
4453
  convoId: ctx.convoId,
4061
- input: patch.input,
4454
+ input,
4062
4455
  output,
4063
4456
  model: finalModel,
4064
4457
  properties: patch.properties,
@@ -4398,7 +4791,7 @@ function wrapModel(args, aiSDK, outerOperationId, ctx) {
4398
4791
  attributes: [
4399
4792
  ...((_a = ctx.telemetry) == null ? void 0 : _a.recordOutputs) === false ? [] : [
4400
4793
  attrString("ai.response.finishReason", finishReason),
4401
- attrString("ai.response.text", activeText.length ? activeText : void 0),
4794
+ attrString("ai.response.text", activeText.length ? capText2(activeText) : void 0),
4402
4795
  attrString(
4403
4796
  "ai.response.toolCalls",
4404
4797
  safeJsonWithUint8(toolCallsLocal.length ? toolCallsLocal : void 0)
@@ -4451,7 +4844,9 @@ function wrapModel(args, aiSDK, outerOperationId, ctx) {
4451
4844
  } else if (typeof value["textDelta"] === "string") {
4452
4845
  textDelta = value["textDelta"];
4453
4846
  }
4454
- if (typeof textDelta === "string") activeText += textDelta;
4847
+ if (typeof textDelta === "string" && activeText.length <= effectiveMaxTextFieldChars()) {
4848
+ activeText += textDelta;
4849
+ }
4455
4850
  }
4456
4851
  if (type === "finish") finishReason = extractFinishReason(value);
4457
4852
  if (type === "tool-call") toolCallsLocal.push(value);
@@ -4724,8 +5119,8 @@ function eventMetadataFromChatRequest(options) {
4724
5119
  });
4725
5120
  }
4726
5121
  function stringify(v) {
4727
- if (typeof v === "string") return v;
4728
- return JSON.stringify(v);
5122
+ if (typeof v === "string") return capText2(v);
5123
+ return boundedStringify(v);
4729
5124
  }
4730
5125
  function userAttrsToOtlp(attrs) {
4731
5126
  if (!attrs) return [];
@@ -4743,6 +5138,7 @@ function createRaindropAISDK(opts) {
4743
5138
  const eventsRequested = ((_a = opts.events) == null ? void 0 : _a.enabled) !== false;
4744
5139
  const tracesRequested = ((_b = opts.traces) == null ? void 0 : _b.enabled) !== false;
4745
5140
  const envDebug = envDebugEnabled();
5141
+ setMaxTextFieldChars(opts.maxTextFieldChars);
4746
5142
  const localWorkshopInput = opts.localWorkshopUrl === false ? null : opts.localWorkshopUrl;
4747
5143
  const resolvedLocalDebuggerUrl = resolveLocalDebuggerBaseUrl(localWorkshopInput);
4748
5144
  const localDebuggerUrl = localWorkshopInput === null ? null : resolvedLocalDebuggerUrl != null ? resolvedLocalDebuggerUrl : void 0;
@@ -4760,6 +5156,7 @@ function createRaindropAISDK(opts) {
4760
5156
  enabled: eventsEnabled,
4761
5157
  debug: ((_c = opts.events) == null ? void 0 : _c.debug) === true || envDebug,
4762
5158
  partialFlushMs: (_d = opts.events) == null ? void 0 : _d.partialFlushMs,
5159
+ projectId: opts.projectId,
4763
5160
  localDebuggerUrl
4764
5161
  });
4765
5162
  const traceShipper = new TraceShipper2({
@@ -4771,6 +5168,7 @@ function createRaindropAISDK(opts) {
4771
5168
  flushIntervalMs: (_g = opts.traces) == null ? void 0 : _g.flushIntervalMs,
4772
5169
  maxBatchSize: (_h = opts.traces) == null ? void 0 : _h.maxBatchSize,
4773
5170
  maxQueueSize: (_i = opts.traces) == null ? void 0 : _i.maxQueueSize,
5171
+ projectId: opts.projectId,
4774
5172
  localDebuggerUrl,
4775
5173
  transformSpan: (_j = opts.traces) == null ? void 0 : _j.transformSpan,
4776
5174
  disableDefaultRedaction: (_k = opts.traces) == null ? void 0 : _k.disableDefaultRedaction
@@ -4929,13 +5327,17 @@ function raindrop(options = {}) {
4929
5327
  return client.createTelemetryIntegration({ context, subagentWrapping });
4930
5328
  }
4931
5329
 
5330
+ exports.DEFAULT_MAX_TEXT_FIELD_CHARS = DEFAULT_MAX_TEXT_FIELD_CHARS2;
4932
5331
  exports.DEFAULT_REDACT_ATTRIBUTE_KEYS = DEFAULT_REDACT_ATTRIBUTE_KEYS;
4933
5332
  exports.DEFAULT_SECRET_KEY_NAMES = DEFAULT_SECRET_KEY_NAMES;
4934
5333
  exports.REDACTED_PLACEHOLDER = REDACTED_PLACEHOLDER;
4935
5334
  exports.RaindropTelemetryIntegration = RaindropTelemetryIntegration;
5335
+ exports.TRUNCATION_MARKER = TRUNCATION_MARKER2;
4936
5336
  exports._resetParentToolContextStorage = _resetParentToolContextStorage;
4937
5337
  exports._resetRaindropCallMetadataStorage = _resetRaindropCallMetadataStorage;
4938
5338
  exports._resetWarnedMissingUserId = _resetWarnedMissingUserId;
5339
+ exports.boundedStringify = boundedStringify;
5340
+ exports.capText = capText2;
4939
5341
  exports.clearParentToolContext = clearParentToolContext;
4940
5342
  exports.createRaindropAISDK = createRaindropAISDK;
4941
5343
  exports.currentSpan = currentSpan;