@raindrop-ai/langchain 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -28,7 +28,7 @@ module.exports = __toCommonJS(index_exports);
28
28
  // src/callback-handler.ts
29
29
  var import_base = require("@langchain/core/callbacks/base");
30
30
 
31
- // ../core/dist/chunk-4UCYIEH4.js
31
+ // ../core/dist/chunk-U5HUTMR5.js
32
32
  function getCrypto() {
33
33
  const c = globalThis.crypto;
34
34
  return c;
@@ -83,6 +83,8 @@ function base64Encode(bytes) {
83
83
  }
84
84
  return out;
85
85
  }
86
+ var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
87
+ var MAX_RETRY_DELAY_MS = 3e4;
86
88
  function wait(ms) {
87
89
  return new Promise((resolve) => setTimeout(resolve, ms));
88
90
  }
@@ -90,6 +92,46 @@ function formatEndpoint(endpoint) {
90
92
  if (!endpoint) return void 0;
91
93
  return endpoint.endsWith("/") ? endpoint : `${endpoint}/`;
92
94
  }
95
+ function redactUrlForLog(url) {
96
+ try {
97
+ const parsed = new URL(url);
98
+ parsed.username = "";
99
+ parsed.password = "";
100
+ parsed.search = "";
101
+ parsed.hash = "";
102
+ return parsed.toString();
103
+ } catch (e) {
104
+ return "<unparseable-url>";
105
+ }
106
+ }
107
+ var RATE_LIMITED_LOG_INTERVAL_MS = 3e4;
108
+ var rateLimitedLogLast = /* @__PURE__ */ new Map();
109
+ function rateLimitedLog(key, log) {
110
+ const now = Date.now();
111
+ const last = rateLimitedLogLast.get(key);
112
+ if (last !== void 0 && now - last < RATE_LIMITED_LOG_INTERVAL_MS) {
113
+ return false;
114
+ }
115
+ rateLimitedLogLast.set(key, now);
116
+ log();
117
+ return true;
118
+ }
119
+ async function raceWithTimeout(promise, timeoutMs) {
120
+ let timer;
121
+ const settledInTime = await Promise.race([
122
+ promise.then(
123
+ () => true,
124
+ () => true
125
+ ),
126
+ new Promise((resolve) => {
127
+ var _a;
128
+ timer = setTimeout(() => resolve(false), Math.max(0, timeoutMs));
129
+ (_a = timer.unref) == null ? void 0 : _a.call(timer);
130
+ })
131
+ ]);
132
+ if (timer) clearTimeout(timer);
133
+ return settledInTime;
134
+ }
93
135
  function parseRetryAfter(headers) {
94
136
  var _a;
95
137
  const value = (_a = headers.get("Retry-After")) != null ? _a : headers.get("retry-after");
@@ -106,12 +148,12 @@ function parseRetryAfter(headers) {
106
148
  function getRetryDelayMs(attemptNumber, previousError) {
107
149
  if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
108
150
  const v = previousError.retryAfterMs;
109
- if (typeof v === "number") return Math.max(0, v);
151
+ if (typeof v === "number") return Math.min(Math.max(0, v), MAX_RETRY_DELAY_MS);
110
152
  }
111
153
  if (attemptNumber <= 1) return 0;
112
154
  const base = 500;
113
155
  const factor = Math.pow(2, attemptNumber - 2);
114
- return base * factor;
156
+ return Math.min(base * factor, MAX_RETRY_DELAY_MS);
115
157
  }
116
158
  async function withRetry(operation, opName, opts) {
117
159
  const prefix = opts.sdkName ? `[raindrop-ai/${opts.sdkName}]` : "[raindrop-ai/core]";
@@ -146,7 +188,9 @@ async function withRetry(operation, opName, opts) {
146
188
  throw lastError instanceof Error ? lastError : new Error(String(lastError));
147
189
  }
148
190
  async function postJson(url, body, headers, opts) {
149
- const opName = `POST ${url}`;
191
+ var _a;
192
+ const opName = `POST ${redactUrlForLog(url)}`;
193
+ const timeoutMs = (_a = opts.timeoutMs) != null ? _a : DEFAULT_REQUEST_TIMEOUT_MS;
150
194
  await withRetry(
151
195
  async () => {
152
196
  const resp = await fetch(url, {
@@ -155,7 +199,8 @@ async function postJson(url, body, headers, opts) {
155
199
  "Content-Type": "application/json",
156
200
  ...headers
157
201
  },
158
- body: JSON.stringify(body)
202
+ body: JSON.stringify(body),
203
+ signal: AbortSignal.timeout(timeoutMs)
159
204
  });
160
205
  if (!resp.ok) {
161
206
  const text = await resp.text().catch(() => "");
@@ -172,6 +217,27 @@ async function postJson(url, body, headers, opts) {
172
217
  opts
173
218
  );
174
219
  }
220
+ var DEFAULT_MAX_TEXT_FIELD_CHARS = 1e6;
221
+ var TRUNCATION_MARKER = "...[truncated by raindrop]";
222
+ var currentDefaultMaxTextFieldChars = DEFAULT_MAX_TEXT_FIELD_CHARS;
223
+ function resolveMaxTextFieldChars(value) {
224
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
225
+ return Math.floor(value);
226
+ }
227
+ return currentDefaultMaxTextFieldChars;
228
+ }
229
+ function truncateToLimit(text, limit) {
230
+ if (limit > TRUNCATION_MARKER.length) {
231
+ return text.slice(0, limit - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;
232
+ }
233
+ return text.slice(0, Math.max(0, limit));
234
+ }
235
+ function capText(value, limit) {
236
+ if (typeof value !== "string") return value;
237
+ const max = limit != null ? limit : currentDefaultMaxTextFieldChars;
238
+ if (value.length <= max) return value;
239
+ return truncateToLimit(value, max);
240
+ }
175
241
  var SpanStatusCode = {
176
242
  UNSET: 0,
177
243
  OK: 1,
@@ -229,6 +295,93 @@ function buildExportTraceServiceRequest(spans, serviceName = "raindrop.core", se
229
295
  ]
230
296
  };
231
297
  }
298
+ var LOCAL_DEBUGGER_ENV_VAR = "RAINDROP_LOCAL_DEBUGGER";
299
+ var WORKSHOP_ENV_VAR = "RAINDROP_WORKSHOP";
300
+ var DEFAULT_LOCAL_WORKSHOP_URL = "http://localhost:5899/v1/";
301
+ function readEnvVar(name) {
302
+ var _a;
303
+ try {
304
+ const env = (_a = globalThis == null ? void 0 : globalThis.process) == null ? void 0 : _a.env;
305
+ if (env && typeof env[name] === "string" && env[name].length > 0) {
306
+ return env[name];
307
+ }
308
+ } catch (e) {
309
+ }
310
+ return void 0;
311
+ }
312
+ function readWorkshopEnv() {
313
+ const raw = readEnvVar(WORKSHOP_ENV_VAR);
314
+ if (raw === void 0) return void 0;
315
+ const trimmed = raw.trim();
316
+ if (trimmed.length === 0) return void 0;
317
+ if (/^https?:\/\//i.test(trimmed)) return { url: trimmed };
318
+ if (/^(1|true|yes|on)$/i.test(trimmed)) return "enable";
319
+ if (/^(0|false|no|off)$/i.test(trimmed)) return "disable";
320
+ return void 0;
321
+ }
322
+ function isLocalDevHost(hostname) {
323
+ if (!hostname) return false;
324
+ if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "0.0.0.0" || hostname === "::1") {
325
+ return true;
326
+ }
327
+ if (hostname.endsWith(".localhost")) return true;
328
+ return false;
329
+ }
330
+ function readRuntimeHostname() {
331
+ try {
332
+ const loc = globalThis == null ? void 0 : globalThis.location;
333
+ if (loc && typeof loc.hostname === "string" && loc.hostname.length > 0) {
334
+ return loc.hostname;
335
+ }
336
+ } catch (e) {
337
+ }
338
+ return void 0;
339
+ }
340
+ function shouldAutoEnableLocalWorkshop() {
341
+ if (isLocalDevHost(readRuntimeHostname())) return true;
342
+ if (readEnvVar("NODE_ENV") === "development") return true;
343
+ return false;
344
+ }
345
+ function resolveLocalDebuggerBaseUrl(baseUrl) {
346
+ var _a, _b, _c;
347
+ if (baseUrl === null) return null;
348
+ if (typeof baseUrl === "string" && baseUrl.length > 0) {
349
+ return (_a = formatEndpoint(baseUrl)) != null ? _a : null;
350
+ }
351
+ const explicitUrlEnv = readEnvVar(LOCAL_DEBUGGER_ENV_VAR);
352
+ if (explicitUrlEnv) return (_b = formatEndpoint(explicitUrlEnv)) != null ? _b : null;
353
+ const workshopEnv = readWorkshopEnv();
354
+ if (workshopEnv === "disable") return null;
355
+ if (workshopEnv === "enable") return DEFAULT_LOCAL_WORKSHOP_URL;
356
+ if (workshopEnv && "url" in workshopEnv) return (_c = formatEndpoint(workshopEnv.url)) != null ? _c : null;
357
+ if (shouldAutoEnableLocalWorkshop()) return DEFAULT_LOCAL_WORKSHOP_URL;
358
+ return null;
359
+ }
360
+ function mirrorTraceExportToLocalDebugger(body, options = {}) {
361
+ var _a;
362
+ const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
363
+ if (!baseUrl) return;
364
+ void postJson(`${baseUrl}traces`, body, {}, {
365
+ maxAttempts: 1,
366
+ debug: (_a = options.debug) != null ? _a : false,
367
+ sdkName: options.sdkName
368
+ }).catch(() => {
369
+ });
370
+ }
371
+ function mirrorPartialEventToLocalDebugger(event, options = {}) {
372
+ var _a;
373
+ const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
374
+ if (!baseUrl) return;
375
+ const headers = options.writeKey ? { Authorization: `Bearer ${options.writeKey}` } : {};
376
+ void postJson(`${baseUrl}events/track_partial`, event, headers, {
377
+ maxAttempts: 1,
378
+ debug: (_a = options.debug) != null ? _a : false,
379
+ sdkName: options.sdkName
380
+ }).catch(() => {
381
+ });
382
+ }
383
+ var SHUTDOWN_DEADLINE_MS = 1e4;
384
+ var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
232
385
  function mergePatches(target, source) {
233
386
  var _a, _b, _c, _d;
234
387
  const out = { ...target, ...source };
@@ -246,7 +399,8 @@ var EventShipper = class {
246
399
  this.sticky = /* @__PURE__ */ new Map();
247
400
  this.timers = /* @__PURE__ */ new Map();
248
401
  this.inFlight = /* @__PURE__ */ new Set();
249
- var _a, _b, _c, _d, _e, _f, _g;
402
+ this.hasShutdown = false;
403
+ var _a, _b, _c, _d, _e, _f, _g, _h;
250
404
  this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
251
405
  this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
252
406
  this.enabled = opts.enabled !== false;
@@ -255,11 +409,16 @@ var EventShipper = class {
255
409
  this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
256
410
  this.prefix = `[raindrop-ai/${this.sdkName}]`;
257
411
  this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
412
+ this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
413
+ this.localDebuggerUrl = (_f = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _f : void 0;
414
+ if (this.debug && this.localDebuggerUrl) {
415
+ console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
416
+ }
258
417
  const isNode = typeof process !== "undefined" && typeof process.version === "string";
259
418
  this.context = {
260
419
  library: {
261
- name: (_f = opts.libraryName) != null ? _f : "@raindrop-ai/core",
262
- version: (_g = opts.libraryVersion) != null ? _g : "0.0.0"
420
+ name: (_g = opts.libraryName) != null ? _g : "@raindrop-ai/core",
421
+ version: (_h = opts.libraryVersion) != null ? _h : "0.0.0"
263
422
  },
264
423
  metadata: {
265
424
  jsRuntime: isNode ? "node" : "web",
@@ -273,10 +432,52 @@ var EventShipper = class {
273
432
  authHeaders() {
274
433
  return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
275
434
  }
435
+ /**
436
+ * Build the retry/timeout options for one POST, honoring the shutdown
437
+ * deadline. Returns `null` when the shutdown drain window is exhausted —
438
+ * the caller must drop the payload (with a rate-limited warning) instead
439
+ * of issuing a request that could outlive process exit.
440
+ *
441
+ * Checked fresh on EVERY send, so a shutdown that begins while the flush
442
+ * path is mid-drain takes effect immediately: no further retries, and the
443
+ * per-attempt timeout is clamped to the remaining window. After
444
+ * `shutdown()` returns (deadline cleared, `hasShutdown` still set),
445
+ * sends — late callers, or flush work the deadline abandoned mid-drain —
446
+ * run as a single short attempt rather than regaining the full retry
447
+ * schedule.
448
+ */
449
+ requestOpts() {
450
+ if (this.shutdownDeadlineAt !== void 0) {
451
+ const remainingMs = this.shutdownDeadlineAt - Date.now();
452
+ if (remainingMs <= 0) return null;
453
+ return {
454
+ maxAttempts: 1,
455
+ debug: this.debug,
456
+ sdkName: this.sdkName,
457
+ timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
458
+ };
459
+ }
460
+ if (this.hasShutdown) {
461
+ return {
462
+ maxAttempts: 1,
463
+ debug: this.debug,
464
+ sdkName: this.sdkName,
465
+ timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
466
+ };
467
+ }
468
+ return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
469
+ }
276
470
  async patch(eventId, patch) {
277
471
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
278
472
  if (!this.enabled) return;
279
473
  if (!eventId || !eventId.trim()) return;
474
+ const maxChars = resolveMaxTextFieldChars(this.maxTextFieldCharsOpt);
475
+ if (typeof patch.input === "string" && patch.input.length > maxChars) {
476
+ patch = { ...patch, input: capText(patch.input, maxChars) };
477
+ }
478
+ if (typeof patch.output === "string" && patch.output.length > maxChars) {
479
+ patch = { ...patch, output: capText(patch.output, maxChars) };
480
+ }
280
481
  if (this.debug) {
281
482
  console.log(`${this.prefix} queue patch`, {
282
483
  eventId,
@@ -323,9 +524,16 @@ var EventShipper = class {
323
524
  })));
324
525
  }
325
526
  async shutdown() {
326
- for (const t of this.timers.values()) clearTimeout(t);
327
- this.timers.clear();
328
- await this.flush();
527
+ this.hasShutdown = true;
528
+ this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
529
+ try {
530
+ for (const t of this.timers.values()) clearTimeout(t);
531
+ this.timers.clear();
532
+ const settled = await raceWithTimeout(this.flush(), SHUTDOWN_DEADLINE_MS);
533
+ if (!settled) this.warnShutdownDrop("in-flight request(s) at shutdown");
534
+ } finally {
535
+ this.shutdownDeadlineAt = void 0;
536
+ }
329
537
  }
330
538
  async trackSignal(signal) {
331
539
  var _a, _b;
@@ -345,16 +553,21 @@ var EventShipper = class {
345
553
  }
346
554
  }
347
555
  ];
556
+ if (!this.writeKey) return;
348
557
  const url = `${this.baseUrl}signals/track`;
558
+ const opts = this.requestOpts();
559
+ if (!opts) {
560
+ this.warnShutdownDrop("signal");
561
+ return;
562
+ }
349
563
  try {
350
- await postJson(url, body, this.authHeaders(), {
351
- maxAttempts: 3,
352
- debug: this.debug,
353
- sdkName: this.sdkName
354
- });
564
+ await postJson(url, body, this.authHeaders(), opts);
355
565
  } catch (err) {
356
566
  const msg = err instanceof Error ? err.message : String(err);
357
- console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`);
567
+ rateLimitedLog(
568
+ `${this.prefix}.send_signal_failed`,
569
+ () => console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`)
570
+ );
358
571
  }
359
572
  }
360
573
  async identify(users) {
@@ -375,19 +588,30 @@ var EventShipper = class {
375
588
  traits: (_a = user.traits) != null ? _a : {}
376
589
  };
377
590
  });
591
+ if (!this.writeKey) return;
378
592
  if (body.length === 0) return;
379
593
  const url = `${this.baseUrl}users/identify`;
594
+ const opts = this.requestOpts();
595
+ if (!opts) {
596
+ this.warnShutdownDrop("identify");
597
+ return;
598
+ }
380
599
  try {
381
- await postJson(url, body, this.authHeaders(), {
382
- maxAttempts: 3,
383
- debug: this.debug,
384
- sdkName: this.sdkName
385
- });
600
+ await postJson(url, body, this.authHeaders(), opts);
386
601
  } catch (err) {
387
602
  const msg = err instanceof Error ? err.message : String(err);
388
- console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`);
603
+ rateLimitedLog(
604
+ `${this.prefix}.send_identify_failed`,
605
+ () => console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`)
606
+ );
389
607
  }
390
608
  }
609
+ warnShutdownDrop(what) {
610
+ rateLimitedLog(
611
+ `${this.prefix}.shutdown_deadline`,
612
+ () => console.warn(`${this.prefix} shutdown flush deadline exceeded; dropping ${what}`)
613
+ );
614
+ }
391
615
  async flushOne(eventId) {
392
616
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
393
617
  if (!this.enabled) return;
@@ -451,11 +675,25 @@ var EventShipper = class {
451
675
  endpoint: url
452
676
  });
453
677
  }
454
- const p = postJson(url, payload, this.authHeaders(), {
455
- maxAttempts: 3,
456
- debug: this.debug,
457
- sdkName: this.sdkName
458
- });
678
+ if (this.localDebuggerUrl) {
679
+ mirrorPartialEventToLocalDebugger(payload, {
680
+ baseUrl: this.localDebuggerUrl,
681
+ writeKey: this.writeKey,
682
+ debug: this.debug,
683
+ sdkName: this.sdkName
684
+ });
685
+ }
686
+ if (!this.writeKey) {
687
+ if (!isPending) this.sticky.delete(eventId);
688
+ return;
689
+ }
690
+ const opts = this.requestOpts();
691
+ if (!opts) {
692
+ this.warnShutdownDrop(`track_partial ${eventId}`);
693
+ if (!isPending) this.sticky.delete(eventId);
694
+ return;
695
+ }
696
+ const p = postJson(url, payload, this.authHeaders(), opts);
459
697
  this.inFlight.add(p);
460
698
  try {
461
699
  try {
@@ -465,7 +703,10 @@ var EventShipper = class {
465
703
  }
466
704
  } catch (err) {
467
705
  const msg = err instanceof Error ? err.message : String(err);
468
- console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`);
706
+ rateLimitedLog(
707
+ `${this.prefix}.send_track_partial_failed`,
708
+ () => console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`)
709
+ );
469
710
  }
470
711
  } finally {
471
712
  this.inFlight.delete(p);
@@ -475,28 +716,114 @@ var EventShipper = class {
475
716
  }
476
717
  }
477
718
  };
478
- var LOCAL_DEBUGGER_ENV_VAR = "RAINDROP_LOCAL_DEBUGGER";
479
- function resolveLocalDebuggerBaseUrl(baseUrl) {
480
- var _a, _b, _c;
481
- const resolved = (_b = baseUrl != null ? baseUrl : typeof process !== "undefined" ? (_a = process.env) == null ? void 0 : _a[LOCAL_DEBUGGER_ENV_VAR] : void 0) != null ? _b : null;
482
- return resolved ? (_c = formatEndpoint(resolved)) != null ? _c : null : null;
719
+ var DEFAULT_SECRET_KEY_NAMES = [
720
+ "apikey",
721
+ "apisecret",
722
+ "apitoken",
723
+ "secretaccesskey",
724
+ "sessiontoken",
725
+ "privatekey",
726
+ "privatekeyid",
727
+ "clientsecret",
728
+ "accesstoken",
729
+ "refreshtoken",
730
+ "oauthtoken",
731
+ "bearertoken",
732
+ "authorization",
733
+ "password",
734
+ "passphrase"
735
+ ];
736
+ var REDACTED_PLACEHOLDER = "[REDACTED]";
737
+ function normalizeKeyName(name) {
738
+ return name.toLowerCase().replace(/[-_.]/g, "");
483
739
  }
484
- function mirrorTraceExportToLocalDebugger(body, options = {}) {
485
- var _a;
486
- const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
487
- if (!baseUrl) return;
488
- void postJson(`${baseUrl}traces`, body, {}, {
489
- maxAttempts: 1,
490
- debug: (_a = options.debug) != null ? _a : false,
491
- sdkName: options.sdkName
492
- }).catch(() => {
493
- });
740
+ function redactSecretsInObject(value, options) {
741
+ var _a, _b;
742
+ const normalizedSecretSet = buildSecretSet((_a = options == null ? void 0 : options.secretKeyNames) != null ? _a : DEFAULT_SECRET_KEY_NAMES);
743
+ const placeholder = (_b = options == null ? void 0 : options.placeholder) != null ? _b : REDACTED_PLACEHOLDER;
744
+ const seen = /* @__PURE__ */ new WeakSet();
745
+ const walk = (node) => {
746
+ if (node === null || typeof node !== "object") return node;
747
+ if (seen.has(node)) return "[CIRCULAR]";
748
+ seen.add(node);
749
+ if (Array.isArray(node)) {
750
+ return node.map((item) => walk(item));
751
+ }
752
+ const out = {};
753
+ for (const [k, v] of Object.entries(node)) {
754
+ if (normalizedSecretSet.has(normalizeKeyName(k))) {
755
+ out[k] = placeholder;
756
+ } else {
757
+ out[k] = walk(v);
758
+ }
759
+ }
760
+ return out;
761
+ };
762
+ return walk(value);
763
+ }
764
+ function buildSecretSet(names) {
765
+ const set = /* @__PURE__ */ new Set();
766
+ for (const name of names) set.add(normalizeKeyName(name));
767
+ return set;
768
+ }
769
+ var DEFAULT_REDACT_ATTRIBUTE_KEYS = [
770
+ "ai.request.providerOptions",
771
+ "ai.response.providerMetadata"
772
+ ];
773
+ function defaultTransformSpan(span) {
774
+ const attrs = span.attributes;
775
+ if (!attrs || attrs.length === 0) return span;
776
+ let nextAttrs;
777
+ for (let i = 0; i < attrs.length; i++) {
778
+ const attr = attrs[i];
779
+ const redacted = redactJsonAttributeValue(attr.key, attr.value);
780
+ if (redacted === void 0) continue;
781
+ if (!nextAttrs) nextAttrs = attrs.slice();
782
+ nextAttrs[i] = { key: attr.key, value: redacted };
783
+ }
784
+ if (!nextAttrs) return span;
785
+ return { ...span, attributes: nextAttrs };
786
+ }
787
+ var REDACT_JSON_ATTRIBUTE_KEYS = new Set(DEFAULT_REDACT_ATTRIBUTE_KEYS);
788
+ function redactJsonAttributeValue(key, value) {
789
+ if (!REDACT_JSON_ATTRIBUTE_KEYS.has(key)) return void 0;
790
+ const json = value.stringValue;
791
+ if (typeof json !== "string" || json.length === 0) return void 0;
792
+ let parsed;
793
+ try {
794
+ parsed = JSON.parse(json);
795
+ } catch (e) {
796
+ return void 0;
797
+ }
798
+ const scrubbed = redactSecretsInObject(parsed);
799
+ let scrubbedJson;
800
+ try {
801
+ scrubbedJson = JSON.stringify(scrubbed);
802
+ } catch (e) {
803
+ return void 0;
804
+ }
805
+ if (scrubbedJson === json) return void 0;
806
+ return { stringValue: scrubbedJson };
807
+ }
808
+ function applyOtelSpanAttributeLimit(limit) {
809
+ var _a, _b;
810
+ try {
811
+ 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;
812
+ if (!raw) return limit;
813
+ const parsed = Number.parseInt(raw, 10);
814
+ if (Number.isFinite(parsed) && parsed > 0) {
815
+ return Math.min(limit, parsed);
816
+ }
817
+ } catch (e) {
818
+ }
819
+ return limit;
494
820
  }
495
821
  var TraceShipper = class {
496
822
  constructor(opts) {
497
823
  this.queue = [];
498
824
  this.inFlight = /* @__PURE__ */ new Set();
499
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
825
+ this.hasShutdown = false;
826
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
500
827
  this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
501
828
  this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
502
829
  this.enabled = opts.enabled !== false;
@@ -509,13 +836,79 @@ var TraceShipper = class {
509
836
  this.prefix = `[raindrop-ai/${this.sdkName}]`;
510
837
  this.serviceName = (_g = opts.serviceName) != null ? _g : "raindrop.core";
511
838
  this.serviceVersion = (_h = opts.serviceVersion) != null ? _h : "0.0.0";
512
- const localDebugger = typeof process !== "undefined" ? (_i = process.env) == null ? void 0 : _i.RAINDROP_LOCAL_DEBUGGER : void 0;
513
- if (localDebugger) {
514
- this.localDebuggerUrl = (_j = resolveLocalDebuggerBaseUrl(localDebugger)) != null ? _j : void 0;
515
- if (this.debug) {
516
- console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
839
+ this.localDebuggerUrl = (_i = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _i : void 0;
840
+ if (this.debug && this.localDebuggerUrl) {
841
+ console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
842
+ }
843
+ this.transformSpanHook = opts.transformSpan;
844
+ this.disableDefaultRedaction = opts.disableDefaultRedaction === true;
845
+ this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
846
+ }
847
+ /**
848
+ * Cap every string attribute value on the span. O(#attributes) length
849
+ * checks; only oversized values pay a slice. Runs AFTER the redaction
850
+ * pipeline so the default secret-scrub still sees parseable JSON in
851
+ * `ai.request.providerOptions` / `ai.response.providerMetadata` (capping
852
+ * first could cut a JSON blob mid-way, fail the parse, and ship secrets
853
+ * in the surviving prefix).
854
+ *
855
+ * A stricter `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var is honored
856
+ * for span content, matching the Python SDK and the OTel SDK convention.
857
+ */
858
+ capSpanAttributes(span) {
859
+ var _a;
860
+ const maxChars = applyOtelSpanAttributeLimit(
861
+ resolveMaxTextFieldChars(this.maxTextFieldCharsOpt)
862
+ );
863
+ const attrs = span.attributes;
864
+ if (!attrs || attrs.length === 0) return span;
865
+ let nextAttrs;
866
+ for (let i = 0; i < attrs.length; i++) {
867
+ const attr = attrs[i];
868
+ const value = (_a = attr.value) == null ? void 0 : _a.stringValue;
869
+ if (typeof value !== "string" || value.length <= maxChars) continue;
870
+ if (!nextAttrs) nextAttrs = attrs.slice();
871
+ nextAttrs[i] = {
872
+ key: attr.key,
873
+ value: { ...attr.value, stringValue: capText(value, maxChars) }
874
+ };
875
+ }
876
+ if (!nextAttrs) return span;
877
+ return { ...span, attributes: nextAttrs };
878
+ }
879
+ /**
880
+ * Apply the user `transformSpan` hook (if any) followed by the default
881
+ * redactor (unless disabled). Returns either the (possibly new) span to
882
+ * ship, or `null` to drop the span entirely.
883
+ *
884
+ * Ordering: user hook runs first so callers can rewrite the span freely
885
+ * (rename attrs, add new ones, scrub things the default doesn't know
886
+ * about). The default redactor then runs on whatever the user produced,
887
+ * acting as the always-on floor for documented BYOK secrets. If the user
888
+ * sets `disableDefaultRedaction: true`, the floor is skipped.
889
+ *
890
+ * Fail-closed: if the user hook throws, the span is dropped — a buggy
891
+ * hook can never accidentally ship raw, un-redacted spans.
892
+ */
893
+ redactSpan(span) {
894
+ let current = span;
895
+ if (this.transformSpanHook) {
896
+ try {
897
+ const result = this.transformSpanHook(current);
898
+ if (result === null) return null;
899
+ if (result !== void 0) current = result;
900
+ } catch (err) {
901
+ if (this.debug) {
902
+ const msg = err instanceof Error ? err.message : String(err);
903
+ console.warn(`${this.prefix} transformSpan hook threw: ${msg}`);
904
+ }
905
+ return null;
517
906
  }
518
907
  }
908
+ if (!this.disableDefaultRedaction) {
909
+ current = defaultTransformSpan(current);
910
+ }
911
+ return this.capSpanAttributes(current);
519
912
  }
520
913
  isDebugEnabled() {
521
914
  return this.debug;
@@ -533,8 +926,8 @@ var TraceShipper = class {
533
926
  ];
534
927
  if ((_b = args.attributes) == null ? void 0 : _b.length) attrs.push(...args.attributes);
535
928
  const span = { ids, name: args.name, startTimeUnixNano: started, attributes: attrs };
536
- if (this.localDebuggerUrl) {
537
- const openSpan = buildOtlpSpan({
929
+ this.mirrorToLocalDebugger(
930
+ buildOtlpSpan({
538
931
  ids: span.ids,
539
932
  name: span.name,
540
933
  startTimeUnixNano: span.startTimeUnixNano,
@@ -542,16 +935,21 @@ var TraceShipper = class {
542
935
  // placeholder — will be updated on endSpan
543
936
  attributes: span.attributes,
544
937
  status: { code: SpanStatusCode.UNSET }
545
- });
546
- const body = buildExportTraceServiceRequest([openSpan], this.serviceName, this.serviceVersion);
547
- mirrorTraceExportToLocalDebugger(body, {
548
- baseUrl: this.localDebuggerUrl,
549
- debug: false,
550
- sdkName: this.sdkName
551
- });
552
- }
938
+ })
939
+ );
553
940
  return span;
554
941
  }
942
+ mirrorToLocalDebugger(span) {
943
+ if (!this.localDebuggerUrl) return;
944
+ const redacted = this.redactSpan(span);
945
+ if (redacted === null) return;
946
+ const body = buildExportTraceServiceRequest([redacted], this.serviceName, this.serviceVersion);
947
+ mirrorTraceExportToLocalDebugger(body, {
948
+ baseUrl: this.localDebuggerUrl,
949
+ debug: false,
950
+ sdkName: this.sdkName
951
+ });
952
+ }
555
953
  endSpan(span, extra) {
556
954
  var _a, _b;
557
955
  if (span.endTimeUnixNano) return;
@@ -573,14 +971,7 @@ var TraceShipper = class {
573
971
  status
574
972
  });
575
973
  this.enqueue(otlp);
576
- if (this.localDebuggerUrl) {
577
- const body = buildExportTraceServiceRequest([otlp], this.serviceName, this.serviceVersion);
578
- mirrorTraceExportToLocalDebugger(body, {
579
- baseUrl: this.localDebuggerUrl,
580
- debug: false,
581
- sdkName: this.sdkName
582
- });
583
- }
974
+ this.mirrorToLocalDebugger(otlp);
584
975
  }
585
976
  createSpan(args) {
586
977
  var _a;
@@ -598,14 +989,7 @@ var TraceShipper = class {
598
989
  status: args.status
599
990
  });
600
991
  this.enqueue(otlp);
601
- if (this.localDebuggerUrl) {
602
- const body = buildExportTraceServiceRequest([otlp], this.serviceName, this.serviceVersion);
603
- mirrorTraceExportToLocalDebugger(body, {
604
- baseUrl: this.localDebuggerUrl,
605
- debug: false,
606
- sdkName: this.sdkName
607
- });
608
- }
992
+ this.mirrorToLocalDebugger(otlp);
609
993
  }
610
994
  enqueue(span) {
611
995
  if (!this.enabled) return;
@@ -617,10 +1001,12 @@ var TraceShipper = class {
617
1001
  )}`
618
1002
  );
619
1003
  }
1004
+ const redacted = this.redactSpan(span);
1005
+ if (redacted === null) return;
620
1006
  if (this.queue.length >= this.maxQueueSize) {
621
1007
  this.queue.shift();
622
1008
  }
623
- this.queue.push(span);
1009
+ this.queue.push(redacted);
624
1010
  if (this.queue.length >= this.maxBatchSize) {
625
1011
  void this.flush().catch(() => {
626
1012
  });
@@ -642,6 +1028,17 @@ var TraceShipper = class {
642
1028
  }
643
1029
  while (this.queue.length > 0) {
644
1030
  const batch = this.queue.splice(0, this.maxBatchSize);
1031
+ if (!this.writeKey) continue;
1032
+ const opts = this.requestOpts();
1033
+ if (!opts) {
1034
+ rateLimitedLog(
1035
+ `${this.prefix}.shutdown_deadline`,
1036
+ () => console.warn(
1037
+ `${this.prefix} shutdown flush deadline exceeded; dropping ${batch.length} spans`
1038
+ )
1039
+ );
1040
+ continue;
1041
+ }
645
1042
  const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
646
1043
  const url = `${this.baseUrl}traces`;
647
1044
  if (this.debug) {
@@ -650,11 +1047,7 @@ var TraceShipper = class {
650
1047
  endpoint: url
651
1048
  });
652
1049
  }
653
- const p = postJson(url, body, this.authHeaders(), {
654
- maxAttempts: 3,
655
- debug: this.debug,
656
- sdkName: this.sdkName
657
- });
1050
+ const p = postJson(url, body, this.authHeaders(), opts);
658
1051
  this.inFlight.add(p);
659
1052
  try {
660
1053
  try {
@@ -662,21 +1055,61 @@ var TraceShipper = class {
662
1055
  if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
663
1056
  } catch (err) {
664
1057
  const msg = err instanceof Error ? err.message : String(err);
665
- console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`);
1058
+ rateLimitedLog(
1059
+ `${this.prefix}.send_spans_failed`,
1060
+ () => console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`)
1061
+ );
666
1062
  }
667
1063
  } finally {
668
1064
  this.inFlight.delete(p);
669
1065
  }
670
1066
  }
671
1067
  }
1068
+ /** See EventShipper.requestOpts — same shutdown-budget semantics. */
1069
+ requestOpts() {
1070
+ if (this.shutdownDeadlineAt !== void 0) {
1071
+ const remainingMs = this.shutdownDeadlineAt - Date.now();
1072
+ if (remainingMs <= 0) return null;
1073
+ return {
1074
+ maxAttempts: 1,
1075
+ debug: this.debug,
1076
+ sdkName: this.sdkName,
1077
+ timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
1078
+ };
1079
+ }
1080
+ if (this.hasShutdown) {
1081
+ return {
1082
+ maxAttempts: 1,
1083
+ debug: this.debug,
1084
+ sdkName: this.sdkName,
1085
+ timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
1086
+ };
1087
+ }
1088
+ return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
1089
+ }
672
1090
  async shutdown() {
673
- if (this.timer) {
674
- clearTimeout(this.timer);
675
- this.timer = void 0;
1091
+ this.hasShutdown = true;
1092
+ this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
1093
+ try {
1094
+ if (this.timer) {
1095
+ clearTimeout(this.timer);
1096
+ this.timer = void 0;
1097
+ }
1098
+ const drain = async () => {
1099
+ await this.flush();
1100
+ await Promise.all([...this.inFlight].map((p) => p.catch(() => {
1101
+ })));
1102
+ };
1103
+ const settled = await raceWithTimeout(drain(), SHUTDOWN_DEADLINE_MS);
1104
+ if (!settled) {
1105
+ rateLimitedLog(
1106
+ `${this.prefix}.shutdown_deadline`,
1107
+ () => console.warn(`${this.prefix} shutdown flush deadline exceeded; abandoning in-flight spans`)
1108
+ );
1109
+ }
1110
+ } finally {
1111
+ this.shutdownDeadlineAt = void 0;
676
1112
  }
677
- await this.flush();
678
- await Promise.all([...this.inFlight].map((p) => p.catch(() => {
679
- })));
680
1113
  }
681
1114
  };
682
1115
 
@@ -684,6 +1117,133 @@ var TraceShipper = class {
684
1117
  var import_async_hooks = require("async_hooks");
685
1118
  globalThis.RAINDROP_ASYNC_LOCAL_STORAGE = import_async_hooks.AsyncLocalStorage;
686
1119
 
1120
+ // src/truncation.ts
1121
+ var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
1122
+ var DEFAULT_MAX_TEXT_FIELD_CHARS2 = 1e6;
1123
+ var MAX_DEPTH = 12;
1124
+ var configuredMaxTextFieldChars;
1125
+ function setMaxTextFieldChars(limit) {
1126
+ if (limit === void 0) return;
1127
+ if (typeof limit === "number" && Number.isFinite(limit) && limit > 0) {
1128
+ configuredMaxTextFieldChars = Math.floor(limit);
1129
+ } else {
1130
+ console.warn(`[raindrop-ai] maxTextFieldChars=${String(limit)} ignored; must be > 0`);
1131
+ }
1132
+ }
1133
+ function otelEnvLimit() {
1134
+ var _a;
1135
+ if (typeof process === "undefined") return void 0;
1136
+ const raw = (_a = process.env) == null ? void 0 : _a.OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT;
1137
+ if (!raw) return void 0;
1138
+ const parsed = Number.parseInt(raw, 10);
1139
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
1140
+ }
1141
+ function effectiveMaxTextFieldChars() {
1142
+ const base = configuredMaxTextFieldChars != null ? configuredMaxTextFieldChars : DEFAULT_MAX_TEXT_FIELD_CHARS2;
1143
+ const env = otelEnvLimit();
1144
+ return env !== void 0 && env < base ? env : base;
1145
+ }
1146
+ function truncateToLimit2(text, limit) {
1147
+ if (limit > TRUNCATION_MARKER2.length) {
1148
+ return text.slice(0, limit - TRUNCATION_MARKER2.length) + TRUNCATION_MARKER2;
1149
+ }
1150
+ return text.slice(0, limit);
1151
+ }
1152
+ function capText2(value, limit) {
1153
+ if (typeof value !== "string") return value;
1154
+ const max = limit != null ? limit : effectiveMaxTextFieldChars();
1155
+ if (value.length <= max) return value;
1156
+ return truncateToLimit2(value, max);
1157
+ }
1158
+ function boundedClone2(value, budget, depth) {
1159
+ var _a, _b;
1160
+ if (budget.remaining <= 0) return TRUNCATION_MARKER2;
1161
+ if (typeof value === "string") {
1162
+ if (value.length > budget.remaining) {
1163
+ const taken = value.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
1164
+ budget.remaining = 0;
1165
+ return taken;
1166
+ }
1167
+ budget.remaining -= Math.max(value.length, 1);
1168
+ return value;
1169
+ }
1170
+ if (value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
1171
+ budget.remaining -= 8;
1172
+ return value;
1173
+ }
1174
+ if (typeof value !== "object") {
1175
+ budget.remaining -= 1;
1176
+ return value;
1177
+ }
1178
+ if (value instanceof Uint8Array) {
1179
+ const maxBytes = Math.max(0, Math.ceil(budget.remaining * 3 / 4));
1180
+ if (value.byteLength > maxBytes) {
1181
+ const taken = base64Encode(value.subarray(0, maxBytes)) + TRUNCATION_MARKER2;
1182
+ budget.remaining = 0;
1183
+ return taken;
1184
+ }
1185
+ const encoded = base64Encode(value);
1186
+ budget.remaining -= Math.max(encoded.length, 1);
1187
+ return encoded;
1188
+ }
1189
+ if (depth >= MAX_DEPTH) {
1190
+ budget.remaining -= 16;
1191
+ const name = (_b = (_a = value.constructor) == null ? void 0 : _a.name) != null ? _b : "object";
1192
+ return `<max depth: ${name}>`;
1193
+ }
1194
+ const toJSON = value.toJSON;
1195
+ if (typeof toJSON === "function") {
1196
+ budget.remaining -= 2;
1197
+ return boundedClone2(toJSON.call(value), budget, depth + 1);
1198
+ }
1199
+ if (Array.isArray(value)) {
1200
+ budget.remaining -= 2;
1201
+ const out2 = [];
1202
+ for (const item of value) {
1203
+ if (budget.remaining <= 0) {
1204
+ out2.push(TRUNCATION_MARKER2);
1205
+ break;
1206
+ }
1207
+ out2.push(boundedClone2(item, budget, depth + 1));
1208
+ }
1209
+ return out2;
1210
+ }
1211
+ budget.remaining -= 2;
1212
+ const out = {};
1213
+ for (const key in value) {
1214
+ if (!Object.prototype.hasOwnProperty.call(value, key)) continue;
1215
+ if (budget.remaining <= 0) {
1216
+ out["..."] = TRUNCATION_MARKER2;
1217
+ break;
1218
+ }
1219
+ let cappedKey = key;
1220
+ if (key.length > budget.remaining) {
1221
+ cappedKey = key.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
1222
+ budget.remaining = 0;
1223
+ out[cappedKey] = TRUNCATION_MARKER2;
1224
+ break;
1225
+ }
1226
+ budget.remaining -= Math.max(key.length, 1);
1227
+ out[cappedKey] = boundedClone2(value[key], budget, depth + 1);
1228
+ }
1229
+ return out;
1230
+ }
1231
+ function boundedStringify(value, limit) {
1232
+ const max = limit != null ? limit : effectiveMaxTextFieldChars();
1233
+ try {
1234
+ if (typeof value === "string") {
1235
+ const text2 = JSON.stringify(capText2(value, max));
1236
+ return text2.length <= max ? text2 : truncateToLimit2(text2, max);
1237
+ }
1238
+ const pruned = boundedClone2(value, { remaining: max + TRUNCATION_MARKER2.length + 256 }, 0);
1239
+ const text = JSON.stringify(pruned);
1240
+ if (text === void 0) return void 0;
1241
+ return text.length <= max ? text : truncateToLimit2(text, max);
1242
+ } catch (e) {
1243
+ return void 0;
1244
+ }
1245
+ }
1246
+
687
1247
  // src/callback-handler.ts
688
1248
  var LANGGRAPH_INTERNAL_NAMES = /* @__PURE__ */ new Set([
689
1249
  "LangGraph",
@@ -773,7 +1333,7 @@ var RaindropCallbackHandler = class extends import_base.BaseCallbackHandler {
773
1333
  await this.eventShipper.patch(eventId, {
774
1334
  userId: this.userId,
775
1335
  convoId: this.convoId,
776
- input: prompts.join("\n"),
1336
+ input: capText2(prompts.join("\n")),
777
1337
  properties: buildProperties(tags, metadata),
778
1338
  isPending: true
779
1339
  });
@@ -803,7 +1363,7 @@ var RaindropCallbackHandler = class extends import_base.BaseCallbackHandler {
803
1363
  return role === "human";
804
1364
  });
805
1365
  const lastMsg = userMessages.length > 0 ? userMessages[userMessages.length - 1] : allMessages[allMessages.length - 1];
806
- const inputText = lastMsg ? typeof lastMsg.content === "string" ? lastMsg.content : safeStringify(lastMsg.content) : void 0;
1366
+ const inputText = lastMsg ? typeof lastMsg.content === "string" ? capText2(lastMsg.content) : safeStringify(lastMsg.content) : void 0;
807
1367
  await this.eventShipper.patch(eventId, {
808
1368
  userId: this.userId,
809
1369
  convoId: this.convoId,
@@ -833,7 +1393,7 @@ var RaindropCallbackHandler = class extends import_base.BaseCallbackHandler {
833
1393
  if (promptTokens != null) properties["ai.usage.prompt_tokens"] = promptTokens;
834
1394
  if (completionTokens != null) properties["ai.usage.completion_tokens"] = completionTokens;
835
1395
  await this.finalizeEventIfRoot(runId, {
836
- output: outputText,
1396
+ output: capText2(outputText),
837
1397
  model,
838
1398
  properties: Object.keys(properties).length > 0 ? properties : void 0
839
1399
  });
@@ -926,7 +1486,7 @@ var RaindropCallbackHandler = class extends import_base.BaseCallbackHandler {
926
1486
  try {
927
1487
  const span = this.spans.get(runId);
928
1488
  if (span) {
929
- const outputStr = typeof output === "string" ? output : safeStringify(output);
1489
+ const outputStr = typeof output === "string" ? output : safeStringify(output, 4096);
930
1490
  this.traceShipper.endSpan(span, {
931
1491
  attributes: [attrString("tool.output", truncate(outputStr, 4096))]
932
1492
  });
@@ -1011,7 +1571,7 @@ var RaindropCallbackHandler = class extends import_base.BaseCallbackHandler {
1011
1571
  attributes: [
1012
1572
  attrString("agent.tool", action.tool),
1013
1573
  attrString("agent.tool_input", truncate(
1014
- typeof action.toolInput === "string" ? action.toolInput : safeStringify(action.toolInput),
1574
+ typeof action.toolInput === "string" ? action.toolInput : safeStringify(action.toolInput, 4096),
1015
1575
  4096
1016
1576
  ))
1017
1577
  ]
@@ -1053,13 +1613,10 @@ function buildProperties(tags, metadata) {
1053
1613
  if (metadata) Object.assign(props, metadata);
1054
1614
  return props;
1055
1615
  }
1056
- function safeStringify(value) {
1616
+ function safeStringify(value, limit) {
1057
1617
  var _a;
1058
- try {
1059
- return (_a = JSON.stringify(value)) != null ? _a : "";
1060
- } catch (e) {
1061
- return String(value);
1062
- }
1618
+ if (value === void 0) return "";
1619
+ return (_a = boundedStringify(value, limit)) != null ? _a : String(value);
1063
1620
  }
1064
1621
  function truncate(str, maxLen) {
1065
1622
  if (str.length <= maxLen) return str;
@@ -1078,14 +1635,14 @@ function extractLLMMetadata(output) {
1078
1635
  const usage = (_g = usageMeta != null ? usageMeta : tokenUsage) != null ? _g : estimatedTokens;
1079
1636
  const promptTokens = (_i = (_h = usage == null ? void 0 : usage.input_tokens) != null ? _h : usage == null ? void 0 : usage.prompt_tokens) != null ? _i : usage == null ? void 0 : usage.promptTokens;
1080
1637
  const completionTokens = (_k = (_j = usage == null ? void 0 : usage.output_tokens) != null ? _j : usage == null ? void 0 : usage.completion_tokens) != null ? _k : usage == null ? void 0 : usage.completionTokens;
1081
- const outputText = (_m = (_l = gen == null ? void 0 : gen.text) != null ? _l : typeof (message == null ? void 0 : message.content) === "string" ? message.content : void 0) != null ? _m : (message == null ? void 0 : message.content) ? JSON.stringify(message.content) : void 0;
1638
+ const outputText = (_m = (_l = gen == null ? void 0 : gen.text) != null ? _l : typeof (message == null ? void 0 : message.content) === "string" ? message.content : void 0) != null ? _m : (message == null ? void 0 : message.content) ? boundedStringify(message.content) : void 0;
1082
1639
  return { model, promptTokens, completionTokens, outputText };
1083
1640
  }
1084
1641
 
1085
1642
  // package.json
1086
1643
  var package_default = {
1087
1644
  name: "@raindrop-ai/langchain",
1088
- version: "0.0.3",
1645
+ version: "0.0.4",
1089
1646
  description: "Raindrop integration for LangChain",
1090
1647
  main: "dist/index.js",
1091
1648
  module: "dist/index.mjs",
@@ -1159,6 +1716,7 @@ function createRaindropLangChain(opts) {
1159
1716
  var _a, _b;
1160
1717
  const writeKey = opts.writeKey;
1161
1718
  const enabled = !!writeKey;
1719
+ setMaxTextFieldChars(opts.maxTextFieldChars);
1162
1720
  if (!writeKey) {
1163
1721
  console.warn("[raindrop-ai/langchain] writeKey not provided; telemetry shipping is disabled");
1164
1722
  }