@raindrop-ai/langchain 0.0.3 → 0.0.5

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.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/callback-handler.ts
2
2
  import { BaseCallbackHandler } from "@langchain/core/callbacks/base";
3
3
 
4
- // ../core/dist/chunk-4UCYIEH4.js
4
+ // ../core/dist/chunk-SK6EJEO7.js
5
5
  function getCrypto() {
6
6
  const c = globalThis.crypto;
7
7
  return c;
@@ -56,6 +56,8 @@ function base64Encode(bytes) {
56
56
  }
57
57
  return out;
58
58
  }
59
+ var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
60
+ var MAX_RETRY_DELAY_MS = 3e4;
59
61
  function wait(ms) {
60
62
  return new Promise((resolve) => setTimeout(resolve, ms));
61
63
  }
@@ -63,6 +65,46 @@ function formatEndpoint(endpoint) {
63
65
  if (!endpoint) return void 0;
64
66
  return endpoint.endsWith("/") ? endpoint : `${endpoint}/`;
65
67
  }
68
+ function redactUrlForLog(url) {
69
+ try {
70
+ const parsed = new URL(url);
71
+ parsed.username = "";
72
+ parsed.password = "";
73
+ parsed.search = "";
74
+ parsed.hash = "";
75
+ return parsed.toString();
76
+ } catch (e) {
77
+ return "<unparseable-url>";
78
+ }
79
+ }
80
+ var RATE_LIMITED_LOG_INTERVAL_MS = 3e4;
81
+ var rateLimitedLogLast = /* @__PURE__ */ new Map();
82
+ function rateLimitedLog(key, log) {
83
+ const now = Date.now();
84
+ const last = rateLimitedLogLast.get(key);
85
+ if (last !== void 0 && now - last < RATE_LIMITED_LOG_INTERVAL_MS) {
86
+ return false;
87
+ }
88
+ rateLimitedLogLast.set(key, now);
89
+ log();
90
+ return true;
91
+ }
92
+ async function raceWithTimeout(promise, timeoutMs) {
93
+ let timer;
94
+ const settledInTime = await Promise.race([
95
+ promise.then(
96
+ () => true,
97
+ () => true
98
+ ),
99
+ new Promise((resolve) => {
100
+ var _a;
101
+ timer = setTimeout(() => resolve(false), Math.max(0, timeoutMs));
102
+ (_a = timer.unref) == null ? void 0 : _a.call(timer);
103
+ })
104
+ ]);
105
+ if (timer) clearTimeout(timer);
106
+ return settledInTime;
107
+ }
66
108
  function parseRetryAfter(headers) {
67
109
  var _a;
68
110
  const value = (_a = headers.get("Retry-After")) != null ? _a : headers.get("retry-after");
@@ -79,12 +121,12 @@ function parseRetryAfter(headers) {
79
121
  function getRetryDelayMs(attemptNumber, previousError) {
80
122
  if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
81
123
  const v = previousError.retryAfterMs;
82
- if (typeof v === "number") return Math.max(0, v);
124
+ if (typeof v === "number") return Math.min(Math.max(0, v), MAX_RETRY_DELAY_MS);
83
125
  }
84
126
  if (attemptNumber <= 1) return 0;
85
127
  const base = 500;
86
128
  const factor = Math.pow(2, attemptNumber - 2);
87
- return base * factor;
129
+ return Math.min(base * factor, MAX_RETRY_DELAY_MS);
88
130
  }
89
131
  async function withRetry(operation, opName, opts) {
90
132
  const prefix = opts.sdkName ? `[raindrop-ai/${opts.sdkName}]` : "[raindrop-ai/core]";
@@ -119,7 +161,9 @@ async function withRetry(operation, opName, opts) {
119
161
  throw lastError instanceof Error ? lastError : new Error(String(lastError));
120
162
  }
121
163
  async function postJson(url, body, headers, opts) {
122
- const opName = `POST ${url}`;
164
+ var _a;
165
+ const opName = `POST ${redactUrlForLog(url)}`;
166
+ const timeoutMs = (_a = opts.timeoutMs) != null ? _a : DEFAULT_REQUEST_TIMEOUT_MS;
123
167
  await withRetry(
124
168
  async () => {
125
169
  const resp = await fetch(url, {
@@ -128,7 +172,8 @@ async function postJson(url, body, headers, opts) {
128
172
  "Content-Type": "application/json",
129
173
  ...headers
130
174
  },
131
- body: JSON.stringify(body)
175
+ body: JSON.stringify(body),
176
+ signal: AbortSignal.timeout(timeoutMs)
132
177
  });
133
178
  if (!resp.ok) {
134
179
  const text = await resp.text().catch(() => "");
@@ -145,6 +190,27 @@ async function postJson(url, body, headers, opts) {
145
190
  opts
146
191
  );
147
192
  }
193
+ var DEFAULT_MAX_TEXT_FIELD_CHARS = 1e6;
194
+ var TRUNCATION_MARKER = "...[truncated by raindrop]";
195
+ var currentDefaultMaxTextFieldChars = DEFAULT_MAX_TEXT_FIELD_CHARS;
196
+ function resolveMaxTextFieldChars(value) {
197
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
198
+ return Math.floor(value);
199
+ }
200
+ return currentDefaultMaxTextFieldChars;
201
+ }
202
+ function truncateToLimit(text, limit) {
203
+ if (limit > TRUNCATION_MARKER.length) {
204
+ return text.slice(0, limit - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;
205
+ }
206
+ return text.slice(0, Math.max(0, limit));
207
+ }
208
+ function capText(value, limit) {
209
+ if (typeof value !== "string") return value;
210
+ const max = limit != null ? limit : currentDefaultMaxTextFieldChars;
211
+ if (value.length <= max) return value;
212
+ return truncateToLimit(value, max);
213
+ }
148
214
  var SpanStatusCode = {
149
215
  UNSET: 0,
150
216
  OK: 1,
@@ -202,6 +268,112 @@ function buildExportTraceServiceRequest(spans, serviceName = "raindrop.core", se
202
268
  ]
203
269
  };
204
270
  }
271
+ var LOCAL_DEBUGGER_ENV_VAR = "RAINDROP_LOCAL_DEBUGGER";
272
+ var WORKSHOP_ENV_VAR = "RAINDROP_WORKSHOP";
273
+ var DEFAULT_LOCAL_WORKSHOP_URL = "http://localhost:5899/v1/";
274
+ function readEnvVar(name) {
275
+ var _a;
276
+ try {
277
+ const env = (_a = globalThis == null ? void 0 : globalThis.process) == null ? void 0 : _a.env;
278
+ if (env && typeof env[name] === "string" && env[name].length > 0) {
279
+ return env[name];
280
+ }
281
+ } catch (e) {
282
+ }
283
+ return void 0;
284
+ }
285
+ function readWorkshopEnv() {
286
+ const raw = readEnvVar(WORKSHOP_ENV_VAR);
287
+ if (raw === void 0) return void 0;
288
+ const trimmed = raw.trim();
289
+ if (trimmed.length === 0) return void 0;
290
+ if (/^https?:\/\//i.test(trimmed)) return { url: trimmed };
291
+ if (/^(1|true|yes|on)$/i.test(trimmed)) return "enable";
292
+ if (/^(0|false|no|off)$/i.test(trimmed)) return "disable";
293
+ return void 0;
294
+ }
295
+ function isLocalDevHost(hostname) {
296
+ if (!hostname) return false;
297
+ if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "0.0.0.0" || hostname === "::1") {
298
+ return true;
299
+ }
300
+ if (hostname.endsWith(".localhost")) return true;
301
+ return false;
302
+ }
303
+ function readRuntimeHostname() {
304
+ try {
305
+ const loc = globalThis == null ? void 0 : globalThis.location;
306
+ if (loc && typeof loc.hostname === "string" && loc.hostname.length > 0) {
307
+ return loc.hostname;
308
+ }
309
+ } catch (e) {
310
+ }
311
+ return void 0;
312
+ }
313
+ function shouldAutoEnableLocalWorkshop() {
314
+ if (isLocalDevHost(readRuntimeHostname())) return true;
315
+ if (readEnvVar("NODE_ENV") === "development") return true;
316
+ return false;
317
+ }
318
+ function resolveLocalDebuggerBaseUrl(baseUrl) {
319
+ var _a, _b, _c;
320
+ if (baseUrl === null) return null;
321
+ if (typeof baseUrl === "string" && baseUrl.length > 0) {
322
+ return (_a = formatEndpoint(baseUrl)) != null ? _a : null;
323
+ }
324
+ const explicitUrlEnv = readEnvVar(LOCAL_DEBUGGER_ENV_VAR);
325
+ if (explicitUrlEnv) return (_b = formatEndpoint(explicitUrlEnv)) != null ? _b : null;
326
+ const workshopEnv = readWorkshopEnv();
327
+ if (workshopEnv === "disable") return null;
328
+ if (workshopEnv === "enable") return DEFAULT_LOCAL_WORKSHOP_URL;
329
+ if (workshopEnv && "url" in workshopEnv) return (_c = formatEndpoint(workshopEnv.url)) != null ? _c : null;
330
+ if (shouldAutoEnableLocalWorkshop()) return DEFAULT_LOCAL_WORKSHOP_URL;
331
+ return null;
332
+ }
333
+ function mirrorTraceExportToLocalDebugger(body, options = {}) {
334
+ var _a;
335
+ const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
336
+ if (!baseUrl) return;
337
+ void postJson(`${baseUrl}traces`, body, {}, {
338
+ maxAttempts: 1,
339
+ debug: (_a = options.debug) != null ? _a : false,
340
+ sdkName: options.sdkName
341
+ }).catch(() => {
342
+ });
343
+ }
344
+ function mirrorPartialEventToLocalDebugger(event, options = {}) {
345
+ var _a;
346
+ const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
347
+ if (!baseUrl) return;
348
+ const headers = options.writeKey ? { Authorization: `Bearer ${options.writeKey}` } : {};
349
+ void postJson(`${baseUrl}events/track_partial`, event, headers, {
350
+ maxAttempts: 1,
351
+ debug: (_a = options.debug) != null ? _a : false,
352
+ sdkName: options.sdkName
353
+ }).catch(() => {
354
+ });
355
+ }
356
+ var PROJECT_ID_HEADER = "X-Raindrop-Project-Id";
357
+ var PROJECT_ID_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
358
+ function isValidProjectIdSlug(value) {
359
+ return PROJECT_ID_SLUG_PATTERN.test(value);
360
+ }
361
+ function normalizeProjectId(raw, opts) {
362
+ if (typeof raw !== "string") return void 0;
363
+ const trimmed = raw.trim();
364
+ if (!trimmed) return void 0;
365
+ if (!isValidProjectIdSlug(trimmed) && opts.debug) {
366
+ console.warn(
367
+ `${opts.prefix} projectId "${trimmed}" does not match slug ${PROJECT_ID_SLUG_PATTERN.source}; sending anyway \u2014 backend may reject with HTTP 400`
368
+ );
369
+ }
370
+ return trimmed;
371
+ }
372
+ function projectIdHeaders(projectId) {
373
+ return projectId ? { [PROJECT_ID_HEADER]: projectId } : {};
374
+ }
375
+ var SHUTDOWN_DEADLINE_MS = 1e4;
376
+ var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
205
377
  function mergePatches(target, source) {
206
378
  var _a, _b, _c, _d;
207
379
  const out = { ...target, ...source };
@@ -219,7 +391,8 @@ var EventShipper = class {
219
391
  this.sticky = /* @__PURE__ */ new Map();
220
392
  this.timers = /* @__PURE__ */ new Map();
221
393
  this.inFlight = /* @__PURE__ */ new Set();
222
- var _a, _b, _c, _d, _e, _f, _g;
394
+ this.hasShutdown = false;
395
+ var _a, _b, _c, _d, _e, _f, _g, _h;
223
396
  this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
224
397
  this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
225
398
  this.enabled = opts.enabled !== false;
@@ -228,11 +401,20 @@ var EventShipper = class {
228
401
  this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
229
402
  this.prefix = `[raindrop-ai/${this.sdkName}]`;
230
403
  this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
404
+ this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
405
+ this.localDebuggerUrl = (_f = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _f : void 0;
406
+ if (this.debug && this.localDebuggerUrl) {
407
+ console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
408
+ }
409
+ this.projectId = normalizeProjectId(opts.projectId, {
410
+ debug: this.debug,
411
+ prefix: this.prefix
412
+ });
231
413
  const isNode = typeof process !== "undefined" && typeof process.version === "string";
232
414
  this.context = {
233
415
  library: {
234
- name: (_f = opts.libraryName) != null ? _f : "@raindrop-ai/core",
235
- version: (_g = opts.libraryVersion) != null ? _g : "0.0.0"
416
+ name: (_g = opts.libraryName) != null ? _g : "@raindrop-ai/core",
417
+ version: (_h = opts.libraryVersion) != null ? _h : "0.0.0"
236
418
  },
237
419
  metadata: {
238
420
  jsRuntime: isNode ? "node" : "web",
@@ -246,10 +428,55 @@ var EventShipper = class {
246
428
  authHeaders() {
247
429
  return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
248
430
  }
431
+ requestHeaders() {
432
+ return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
433
+ }
434
+ /**
435
+ * Build the retry/timeout options for one POST, honoring the shutdown
436
+ * deadline. Returns `null` when the shutdown drain window is exhausted —
437
+ * the caller must drop the payload (with a rate-limited warning) instead
438
+ * of issuing a request that could outlive process exit.
439
+ *
440
+ * Checked fresh on EVERY send, so a shutdown that begins while the flush
441
+ * path is mid-drain takes effect immediately: no further retries, and the
442
+ * per-attempt timeout is clamped to the remaining window. After
443
+ * `shutdown()` returns (deadline cleared, `hasShutdown` still set),
444
+ * sends — late callers, or flush work the deadline abandoned mid-drain —
445
+ * run as a single short attempt rather than regaining the full retry
446
+ * schedule.
447
+ */
448
+ requestOpts() {
449
+ if (this.shutdownDeadlineAt !== void 0) {
450
+ const remainingMs = this.shutdownDeadlineAt - Date.now();
451
+ if (remainingMs <= 0) return null;
452
+ return {
453
+ maxAttempts: 1,
454
+ debug: this.debug,
455
+ sdkName: this.sdkName,
456
+ timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
457
+ };
458
+ }
459
+ if (this.hasShutdown) {
460
+ return {
461
+ maxAttempts: 1,
462
+ debug: this.debug,
463
+ sdkName: this.sdkName,
464
+ timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
465
+ };
466
+ }
467
+ return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
468
+ }
249
469
  async patch(eventId, patch) {
250
470
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
251
471
  if (!this.enabled) return;
252
472
  if (!eventId || !eventId.trim()) return;
473
+ const maxChars = resolveMaxTextFieldChars(this.maxTextFieldCharsOpt);
474
+ if (typeof patch.input === "string" && patch.input.length > maxChars) {
475
+ patch = { ...patch, input: capText(patch.input, maxChars) };
476
+ }
477
+ if (typeof patch.output === "string" && patch.output.length > maxChars) {
478
+ patch = { ...patch, output: capText(patch.output, maxChars) };
479
+ }
253
480
  if (this.debug) {
254
481
  console.log(`${this.prefix} queue patch`, {
255
482
  eventId,
@@ -296,9 +523,16 @@ var EventShipper = class {
296
523
  })));
297
524
  }
298
525
  async shutdown() {
299
- for (const t of this.timers.values()) clearTimeout(t);
300
- this.timers.clear();
301
- await this.flush();
526
+ this.hasShutdown = true;
527
+ this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
528
+ try {
529
+ for (const t of this.timers.values()) clearTimeout(t);
530
+ this.timers.clear();
531
+ const settled = await raceWithTimeout(this.flush(), SHUTDOWN_DEADLINE_MS);
532
+ if (!settled) this.warnShutdownDrop("in-flight request(s) at shutdown");
533
+ } finally {
534
+ this.shutdownDeadlineAt = void 0;
535
+ }
302
536
  }
303
537
  async trackSignal(signal) {
304
538
  var _a, _b;
@@ -318,16 +552,21 @@ var EventShipper = class {
318
552
  }
319
553
  }
320
554
  ];
555
+ if (!this.writeKey) return;
321
556
  const url = `${this.baseUrl}signals/track`;
557
+ const opts = this.requestOpts();
558
+ if (!opts) {
559
+ this.warnShutdownDrop("signal");
560
+ return;
561
+ }
322
562
  try {
323
- await postJson(url, body, this.authHeaders(), {
324
- maxAttempts: 3,
325
- debug: this.debug,
326
- sdkName: this.sdkName
327
- });
563
+ await postJson(url, body, this.requestHeaders(), opts);
328
564
  } catch (err) {
329
565
  const msg = err instanceof Error ? err.message : String(err);
330
- console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`);
566
+ rateLimitedLog(
567
+ `${this.prefix}.send_signal_failed`,
568
+ () => console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`)
569
+ );
331
570
  }
332
571
  }
333
572
  async identify(users) {
@@ -348,19 +587,30 @@ var EventShipper = class {
348
587
  traits: (_a = user.traits) != null ? _a : {}
349
588
  };
350
589
  });
590
+ if (!this.writeKey) return;
351
591
  if (body.length === 0) return;
352
592
  const url = `${this.baseUrl}users/identify`;
593
+ const opts = this.requestOpts();
594
+ if (!opts) {
595
+ this.warnShutdownDrop("identify");
596
+ return;
597
+ }
353
598
  try {
354
- await postJson(url, body, this.authHeaders(), {
355
- maxAttempts: 3,
356
- debug: this.debug,
357
- sdkName: this.sdkName
358
- });
599
+ await postJson(url, body, this.requestHeaders(), opts);
359
600
  } catch (err) {
360
601
  const msg = err instanceof Error ? err.message : String(err);
361
- console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`);
602
+ rateLimitedLog(
603
+ `${this.prefix}.send_identify_failed`,
604
+ () => console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`)
605
+ );
362
606
  }
363
607
  }
608
+ warnShutdownDrop(what) {
609
+ rateLimitedLog(
610
+ `${this.prefix}.shutdown_deadline`,
611
+ () => console.warn(`${this.prefix} shutdown flush deadline exceeded; dropping ${what}`)
612
+ );
613
+ }
364
614
  async flushOne(eventId) {
365
615
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
366
616
  if (!this.enabled) return;
@@ -424,11 +674,25 @@ var EventShipper = class {
424
674
  endpoint: url
425
675
  });
426
676
  }
427
- const p = postJson(url, payload, this.authHeaders(), {
428
- maxAttempts: 3,
429
- debug: this.debug,
430
- sdkName: this.sdkName
431
- });
677
+ if (this.localDebuggerUrl) {
678
+ mirrorPartialEventToLocalDebugger(payload, {
679
+ baseUrl: this.localDebuggerUrl,
680
+ writeKey: this.writeKey,
681
+ debug: this.debug,
682
+ sdkName: this.sdkName
683
+ });
684
+ }
685
+ if (!this.writeKey) {
686
+ if (!isPending) this.sticky.delete(eventId);
687
+ return;
688
+ }
689
+ const opts = this.requestOpts();
690
+ if (!opts) {
691
+ this.warnShutdownDrop(`track_partial ${eventId}`);
692
+ if (!isPending) this.sticky.delete(eventId);
693
+ return;
694
+ }
695
+ const p = postJson(url, payload, this.requestHeaders(), opts);
432
696
  this.inFlight.add(p);
433
697
  try {
434
698
  try {
@@ -438,7 +702,10 @@ var EventShipper = class {
438
702
  }
439
703
  } catch (err) {
440
704
  const msg = err instanceof Error ? err.message : String(err);
441
- console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`);
705
+ rateLimitedLog(
706
+ `${this.prefix}.send_track_partial_failed`,
707
+ () => console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`)
708
+ );
442
709
  }
443
710
  } finally {
444
711
  this.inFlight.delete(p);
@@ -448,28 +715,114 @@ var EventShipper = class {
448
715
  }
449
716
  }
450
717
  };
451
- var LOCAL_DEBUGGER_ENV_VAR = "RAINDROP_LOCAL_DEBUGGER";
452
- function resolveLocalDebuggerBaseUrl(baseUrl) {
453
- var _a, _b, _c;
454
- 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;
455
- return resolved ? (_c = formatEndpoint(resolved)) != null ? _c : null : null;
718
+ var DEFAULT_SECRET_KEY_NAMES = [
719
+ "apikey",
720
+ "apisecret",
721
+ "apitoken",
722
+ "secretaccesskey",
723
+ "sessiontoken",
724
+ "privatekey",
725
+ "privatekeyid",
726
+ "clientsecret",
727
+ "accesstoken",
728
+ "refreshtoken",
729
+ "oauthtoken",
730
+ "bearertoken",
731
+ "authorization",
732
+ "password",
733
+ "passphrase"
734
+ ];
735
+ var REDACTED_PLACEHOLDER = "[REDACTED]";
736
+ function normalizeKeyName(name) {
737
+ return name.toLowerCase().replace(/[-_.]/g, "");
456
738
  }
457
- function mirrorTraceExportToLocalDebugger(body, options = {}) {
458
- var _a;
459
- const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
460
- if (!baseUrl) return;
461
- void postJson(`${baseUrl}traces`, body, {}, {
462
- maxAttempts: 1,
463
- debug: (_a = options.debug) != null ? _a : false,
464
- sdkName: options.sdkName
465
- }).catch(() => {
466
- });
739
+ function redactSecretsInObject(value, options) {
740
+ var _a, _b;
741
+ const normalizedSecretSet = buildSecretSet((_a = options == null ? void 0 : options.secretKeyNames) != null ? _a : DEFAULT_SECRET_KEY_NAMES);
742
+ const placeholder = (_b = options == null ? void 0 : options.placeholder) != null ? _b : REDACTED_PLACEHOLDER;
743
+ const seen = /* @__PURE__ */ new WeakSet();
744
+ const walk = (node) => {
745
+ if (node === null || typeof node !== "object") return node;
746
+ if (seen.has(node)) return "[CIRCULAR]";
747
+ seen.add(node);
748
+ if (Array.isArray(node)) {
749
+ return node.map((item) => walk(item));
750
+ }
751
+ const out = {};
752
+ for (const [k, v] of Object.entries(node)) {
753
+ if (normalizedSecretSet.has(normalizeKeyName(k))) {
754
+ out[k] = placeholder;
755
+ } else {
756
+ out[k] = walk(v);
757
+ }
758
+ }
759
+ return out;
760
+ };
761
+ return walk(value);
762
+ }
763
+ function buildSecretSet(names) {
764
+ const set = /* @__PURE__ */ new Set();
765
+ for (const name of names) set.add(normalizeKeyName(name));
766
+ return set;
767
+ }
768
+ var DEFAULT_REDACT_ATTRIBUTE_KEYS = [
769
+ "ai.request.providerOptions",
770
+ "ai.response.providerMetadata"
771
+ ];
772
+ function defaultTransformSpan(span) {
773
+ const attrs = span.attributes;
774
+ if (!attrs || attrs.length === 0) return span;
775
+ let nextAttrs;
776
+ for (let i = 0; i < attrs.length; i++) {
777
+ const attr = attrs[i];
778
+ const redacted = redactJsonAttributeValue(attr.key, attr.value);
779
+ if (redacted === void 0) continue;
780
+ if (!nextAttrs) nextAttrs = attrs.slice();
781
+ nextAttrs[i] = { key: attr.key, value: redacted };
782
+ }
783
+ if (!nextAttrs) return span;
784
+ return { ...span, attributes: nextAttrs };
785
+ }
786
+ var REDACT_JSON_ATTRIBUTE_KEYS = new Set(DEFAULT_REDACT_ATTRIBUTE_KEYS);
787
+ function redactJsonAttributeValue(key, value) {
788
+ if (!REDACT_JSON_ATTRIBUTE_KEYS.has(key)) return void 0;
789
+ const json = value.stringValue;
790
+ if (typeof json !== "string" || json.length === 0) return void 0;
791
+ let parsed;
792
+ try {
793
+ parsed = JSON.parse(json);
794
+ } catch (e) {
795
+ return void 0;
796
+ }
797
+ const scrubbed = redactSecretsInObject(parsed);
798
+ let scrubbedJson;
799
+ try {
800
+ scrubbedJson = JSON.stringify(scrubbed);
801
+ } catch (e) {
802
+ return void 0;
803
+ }
804
+ if (scrubbedJson === json) return void 0;
805
+ return { stringValue: scrubbedJson };
806
+ }
807
+ function applyOtelSpanAttributeLimit(limit) {
808
+ var _a, _b;
809
+ try {
810
+ 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;
811
+ if (!raw) return limit;
812
+ const parsed = Number.parseInt(raw, 10);
813
+ if (Number.isFinite(parsed) && parsed > 0) {
814
+ return Math.min(limit, parsed);
815
+ }
816
+ } catch (e) {
817
+ }
818
+ return limit;
467
819
  }
468
820
  var TraceShipper = class {
469
821
  constructor(opts) {
470
822
  this.queue = [];
471
823
  this.inFlight = /* @__PURE__ */ new Set();
472
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
824
+ this.hasShutdown = false;
825
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
473
826
  this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
474
827
  this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
475
828
  this.enabled = opts.enabled !== false;
@@ -482,13 +835,83 @@ var TraceShipper = class {
482
835
  this.prefix = `[raindrop-ai/${this.sdkName}]`;
483
836
  this.serviceName = (_g = opts.serviceName) != null ? _g : "raindrop.core";
484
837
  this.serviceVersion = (_h = opts.serviceVersion) != null ? _h : "0.0.0";
485
- const localDebugger = typeof process !== "undefined" ? (_i = process.env) == null ? void 0 : _i.RAINDROP_LOCAL_DEBUGGER : void 0;
486
- if (localDebugger) {
487
- this.localDebuggerUrl = (_j = resolveLocalDebuggerBaseUrl(localDebugger)) != null ? _j : void 0;
488
- if (this.debug) {
489
- console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
838
+ this.localDebuggerUrl = (_i = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _i : void 0;
839
+ if (this.debug && this.localDebuggerUrl) {
840
+ console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
841
+ }
842
+ this.projectId = normalizeProjectId(opts.projectId, {
843
+ debug: this.debug,
844
+ prefix: this.prefix
845
+ });
846
+ this.transformSpanHook = opts.transformSpan;
847
+ this.disableDefaultRedaction = opts.disableDefaultRedaction === true;
848
+ this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
849
+ }
850
+ /**
851
+ * Cap every string attribute value on the span. O(#attributes) length
852
+ * checks; only oversized values pay a slice. Runs AFTER the redaction
853
+ * pipeline so the default secret-scrub still sees parseable JSON in
854
+ * `ai.request.providerOptions` / `ai.response.providerMetadata` (capping
855
+ * first could cut a JSON blob mid-way, fail the parse, and ship secrets
856
+ * in the surviving prefix).
857
+ *
858
+ * A stricter `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var is honored
859
+ * for span content, matching the Python SDK and the OTel SDK convention.
860
+ */
861
+ capSpanAttributes(span) {
862
+ var _a;
863
+ const maxChars = applyOtelSpanAttributeLimit(
864
+ resolveMaxTextFieldChars(this.maxTextFieldCharsOpt)
865
+ );
866
+ const attrs = span.attributes;
867
+ if (!attrs || attrs.length === 0) return span;
868
+ let nextAttrs;
869
+ for (let i = 0; i < attrs.length; i++) {
870
+ const attr = attrs[i];
871
+ const value = (_a = attr.value) == null ? void 0 : _a.stringValue;
872
+ if (typeof value !== "string" || value.length <= maxChars) continue;
873
+ if (!nextAttrs) nextAttrs = attrs.slice();
874
+ nextAttrs[i] = {
875
+ key: attr.key,
876
+ value: { ...attr.value, stringValue: capText(value, maxChars) }
877
+ };
878
+ }
879
+ if (!nextAttrs) return span;
880
+ return { ...span, attributes: nextAttrs };
881
+ }
882
+ /**
883
+ * Apply the user `transformSpan` hook (if any) followed by the default
884
+ * redactor (unless disabled). Returns either the (possibly new) span to
885
+ * ship, or `null` to drop the span entirely.
886
+ *
887
+ * Ordering: user hook runs first so callers can rewrite the span freely
888
+ * (rename attrs, add new ones, scrub things the default doesn't know
889
+ * about). The default redactor then runs on whatever the user produced,
890
+ * acting as the always-on floor for documented BYOK secrets. If the user
891
+ * sets `disableDefaultRedaction: true`, the floor is skipped.
892
+ *
893
+ * Fail-closed: if the user hook throws, the span is dropped — a buggy
894
+ * hook can never accidentally ship raw, un-redacted spans.
895
+ */
896
+ redactSpan(span) {
897
+ let current = span;
898
+ if (this.transformSpanHook) {
899
+ try {
900
+ const result = this.transformSpanHook(current);
901
+ if (result === null) return null;
902
+ if (result !== void 0) current = result;
903
+ } catch (err) {
904
+ if (this.debug) {
905
+ const msg = err instanceof Error ? err.message : String(err);
906
+ console.warn(`${this.prefix} transformSpan hook threw: ${msg}`);
907
+ }
908
+ return null;
490
909
  }
491
910
  }
911
+ if (!this.disableDefaultRedaction) {
912
+ current = defaultTransformSpan(current);
913
+ }
914
+ return this.capSpanAttributes(current);
492
915
  }
493
916
  isDebugEnabled() {
494
917
  return this.debug;
@@ -496,6 +919,9 @@ var TraceShipper = class {
496
919
  authHeaders() {
497
920
  return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
498
921
  }
922
+ requestHeaders() {
923
+ return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
924
+ }
499
925
  startSpan(args) {
500
926
  var _a, _b;
501
927
  const ids = createSpanIds(args.parent);
@@ -506,8 +932,8 @@ var TraceShipper = class {
506
932
  ];
507
933
  if ((_b = args.attributes) == null ? void 0 : _b.length) attrs.push(...args.attributes);
508
934
  const span = { ids, name: args.name, startTimeUnixNano: started, attributes: attrs };
509
- if (this.localDebuggerUrl) {
510
- const openSpan = buildOtlpSpan({
935
+ this.mirrorToLocalDebugger(
936
+ buildOtlpSpan({
511
937
  ids: span.ids,
512
938
  name: span.name,
513
939
  startTimeUnixNano: span.startTimeUnixNano,
@@ -515,16 +941,21 @@ var TraceShipper = class {
515
941
  // placeholder — will be updated on endSpan
516
942
  attributes: span.attributes,
517
943
  status: { code: SpanStatusCode.UNSET }
518
- });
519
- const body = buildExportTraceServiceRequest([openSpan], this.serviceName, this.serviceVersion);
520
- mirrorTraceExportToLocalDebugger(body, {
521
- baseUrl: this.localDebuggerUrl,
522
- debug: false,
523
- sdkName: this.sdkName
524
- });
525
- }
944
+ })
945
+ );
526
946
  return span;
527
947
  }
948
+ mirrorToLocalDebugger(span) {
949
+ if (!this.localDebuggerUrl) return;
950
+ const redacted = this.redactSpan(span);
951
+ if (redacted === null) return;
952
+ const body = buildExportTraceServiceRequest([redacted], this.serviceName, this.serviceVersion);
953
+ mirrorTraceExportToLocalDebugger(body, {
954
+ baseUrl: this.localDebuggerUrl,
955
+ debug: false,
956
+ sdkName: this.sdkName
957
+ });
958
+ }
528
959
  endSpan(span, extra) {
529
960
  var _a, _b;
530
961
  if (span.endTimeUnixNano) return;
@@ -546,14 +977,7 @@ var TraceShipper = class {
546
977
  status
547
978
  });
548
979
  this.enqueue(otlp);
549
- if (this.localDebuggerUrl) {
550
- const body = buildExportTraceServiceRequest([otlp], this.serviceName, this.serviceVersion);
551
- mirrorTraceExportToLocalDebugger(body, {
552
- baseUrl: this.localDebuggerUrl,
553
- debug: false,
554
- sdkName: this.sdkName
555
- });
556
- }
980
+ this.mirrorToLocalDebugger(otlp);
557
981
  }
558
982
  createSpan(args) {
559
983
  var _a;
@@ -571,14 +995,7 @@ var TraceShipper = class {
571
995
  status: args.status
572
996
  });
573
997
  this.enqueue(otlp);
574
- if (this.localDebuggerUrl) {
575
- const body = buildExportTraceServiceRequest([otlp], this.serviceName, this.serviceVersion);
576
- mirrorTraceExportToLocalDebugger(body, {
577
- baseUrl: this.localDebuggerUrl,
578
- debug: false,
579
- sdkName: this.sdkName
580
- });
581
- }
998
+ this.mirrorToLocalDebugger(otlp);
582
999
  }
583
1000
  enqueue(span) {
584
1001
  if (!this.enabled) return;
@@ -590,10 +1007,12 @@ var TraceShipper = class {
590
1007
  )}`
591
1008
  );
592
1009
  }
1010
+ const redacted = this.redactSpan(span);
1011
+ if (redacted === null) return;
593
1012
  if (this.queue.length >= this.maxQueueSize) {
594
1013
  this.queue.shift();
595
1014
  }
596
- this.queue.push(span);
1015
+ this.queue.push(redacted);
597
1016
  if (this.queue.length >= this.maxBatchSize) {
598
1017
  void this.flush().catch(() => {
599
1018
  });
@@ -615,6 +1034,17 @@ var TraceShipper = class {
615
1034
  }
616
1035
  while (this.queue.length > 0) {
617
1036
  const batch = this.queue.splice(0, this.maxBatchSize);
1037
+ if (!this.writeKey) continue;
1038
+ const opts = this.requestOpts();
1039
+ if (!opts) {
1040
+ rateLimitedLog(
1041
+ `${this.prefix}.shutdown_deadline`,
1042
+ () => console.warn(
1043
+ `${this.prefix} shutdown flush deadline exceeded; dropping ${batch.length} spans`
1044
+ )
1045
+ );
1046
+ continue;
1047
+ }
618
1048
  const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
619
1049
  const url = `${this.baseUrl}traces`;
620
1050
  if (this.debug) {
@@ -623,11 +1053,7 @@ var TraceShipper = class {
623
1053
  endpoint: url
624
1054
  });
625
1055
  }
626
- const p = postJson(url, body, this.authHeaders(), {
627
- maxAttempts: 3,
628
- debug: this.debug,
629
- sdkName: this.sdkName
630
- });
1056
+ const p = postJson(url, body, this.requestHeaders(), opts);
631
1057
  this.inFlight.add(p);
632
1058
  try {
633
1059
  try {
@@ -635,21 +1061,61 @@ var TraceShipper = class {
635
1061
  if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
636
1062
  } catch (err) {
637
1063
  const msg = err instanceof Error ? err.message : String(err);
638
- console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`);
1064
+ rateLimitedLog(
1065
+ `${this.prefix}.send_spans_failed`,
1066
+ () => console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`)
1067
+ );
639
1068
  }
640
1069
  } finally {
641
1070
  this.inFlight.delete(p);
642
1071
  }
643
1072
  }
644
1073
  }
1074
+ /** See EventShipper.requestOpts — same shutdown-budget semantics. */
1075
+ requestOpts() {
1076
+ if (this.shutdownDeadlineAt !== void 0) {
1077
+ const remainingMs = this.shutdownDeadlineAt - Date.now();
1078
+ if (remainingMs <= 0) return null;
1079
+ return {
1080
+ maxAttempts: 1,
1081
+ debug: this.debug,
1082
+ sdkName: this.sdkName,
1083
+ timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
1084
+ };
1085
+ }
1086
+ if (this.hasShutdown) {
1087
+ return {
1088
+ maxAttempts: 1,
1089
+ debug: this.debug,
1090
+ sdkName: this.sdkName,
1091
+ timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
1092
+ };
1093
+ }
1094
+ return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
1095
+ }
645
1096
  async shutdown() {
646
- if (this.timer) {
647
- clearTimeout(this.timer);
648
- this.timer = void 0;
1097
+ this.hasShutdown = true;
1098
+ this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
1099
+ try {
1100
+ if (this.timer) {
1101
+ clearTimeout(this.timer);
1102
+ this.timer = void 0;
1103
+ }
1104
+ const drain = async () => {
1105
+ await this.flush();
1106
+ await Promise.all([...this.inFlight].map((p) => p.catch(() => {
1107
+ })));
1108
+ };
1109
+ const settled = await raceWithTimeout(drain(), SHUTDOWN_DEADLINE_MS);
1110
+ if (!settled) {
1111
+ rateLimitedLog(
1112
+ `${this.prefix}.shutdown_deadline`,
1113
+ () => console.warn(`${this.prefix} shutdown flush deadline exceeded; abandoning in-flight spans`)
1114
+ );
1115
+ }
1116
+ } finally {
1117
+ this.shutdownDeadlineAt = void 0;
649
1118
  }
650
- await this.flush();
651
- await Promise.all([...this.inFlight].map((p) => p.catch(() => {
652
- })));
653
1119
  }
654
1120
  };
655
1121
 
@@ -657,6 +1123,133 @@ var TraceShipper = class {
657
1123
  import { AsyncLocalStorage } from "async_hooks";
658
1124
  globalThis.RAINDROP_ASYNC_LOCAL_STORAGE = AsyncLocalStorage;
659
1125
 
1126
+ // src/truncation.ts
1127
+ var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
1128
+ var DEFAULT_MAX_TEXT_FIELD_CHARS2 = 1e6;
1129
+ var MAX_DEPTH = 12;
1130
+ var configuredMaxTextFieldChars;
1131
+ function setMaxTextFieldChars(limit) {
1132
+ if (limit === void 0) return;
1133
+ if (typeof limit === "number" && Number.isFinite(limit) && limit > 0) {
1134
+ configuredMaxTextFieldChars = Math.floor(limit);
1135
+ } else {
1136
+ console.warn(`[raindrop-ai] maxTextFieldChars=${String(limit)} ignored; must be > 0`);
1137
+ }
1138
+ }
1139
+ function otelEnvLimit() {
1140
+ var _a;
1141
+ if (typeof process === "undefined") return void 0;
1142
+ const raw = (_a = process.env) == null ? void 0 : _a.OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT;
1143
+ if (!raw) return void 0;
1144
+ const parsed = Number.parseInt(raw, 10);
1145
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
1146
+ }
1147
+ function effectiveMaxTextFieldChars() {
1148
+ const base = configuredMaxTextFieldChars != null ? configuredMaxTextFieldChars : DEFAULT_MAX_TEXT_FIELD_CHARS2;
1149
+ const env = otelEnvLimit();
1150
+ return env !== void 0 && env < base ? env : base;
1151
+ }
1152
+ function truncateToLimit2(text, limit) {
1153
+ if (limit > TRUNCATION_MARKER2.length) {
1154
+ return text.slice(0, limit - TRUNCATION_MARKER2.length) + TRUNCATION_MARKER2;
1155
+ }
1156
+ return text.slice(0, limit);
1157
+ }
1158
+ function capText2(value, limit) {
1159
+ if (typeof value !== "string") return value;
1160
+ const max = limit != null ? limit : effectiveMaxTextFieldChars();
1161
+ if (value.length <= max) return value;
1162
+ return truncateToLimit2(value, max);
1163
+ }
1164
+ function boundedClone2(value, budget, depth) {
1165
+ var _a, _b;
1166
+ if (budget.remaining <= 0) return TRUNCATION_MARKER2;
1167
+ if (typeof value === "string") {
1168
+ if (value.length > budget.remaining) {
1169
+ const taken = value.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
1170
+ budget.remaining = 0;
1171
+ return taken;
1172
+ }
1173
+ budget.remaining -= Math.max(value.length, 1);
1174
+ return value;
1175
+ }
1176
+ if (value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
1177
+ budget.remaining -= 8;
1178
+ return value;
1179
+ }
1180
+ if (typeof value !== "object") {
1181
+ budget.remaining -= 1;
1182
+ return value;
1183
+ }
1184
+ if (value instanceof Uint8Array) {
1185
+ const maxBytes = Math.max(0, Math.ceil(budget.remaining * 3 / 4));
1186
+ if (value.byteLength > maxBytes) {
1187
+ const taken = base64Encode(value.subarray(0, maxBytes)) + TRUNCATION_MARKER2;
1188
+ budget.remaining = 0;
1189
+ return taken;
1190
+ }
1191
+ const encoded = base64Encode(value);
1192
+ budget.remaining -= Math.max(encoded.length, 1);
1193
+ return encoded;
1194
+ }
1195
+ if (depth >= MAX_DEPTH) {
1196
+ budget.remaining -= 16;
1197
+ const name = (_b = (_a = value.constructor) == null ? void 0 : _a.name) != null ? _b : "object";
1198
+ return `<max depth: ${name}>`;
1199
+ }
1200
+ const toJSON = value.toJSON;
1201
+ if (typeof toJSON === "function") {
1202
+ budget.remaining -= 2;
1203
+ return boundedClone2(toJSON.call(value), budget, depth + 1);
1204
+ }
1205
+ if (Array.isArray(value)) {
1206
+ budget.remaining -= 2;
1207
+ const out2 = [];
1208
+ for (const item of value) {
1209
+ if (budget.remaining <= 0) {
1210
+ out2.push(TRUNCATION_MARKER2);
1211
+ break;
1212
+ }
1213
+ out2.push(boundedClone2(item, budget, depth + 1));
1214
+ }
1215
+ return out2;
1216
+ }
1217
+ budget.remaining -= 2;
1218
+ const out = {};
1219
+ for (const key in value) {
1220
+ if (!Object.prototype.hasOwnProperty.call(value, key)) continue;
1221
+ if (budget.remaining <= 0) {
1222
+ out["..."] = TRUNCATION_MARKER2;
1223
+ break;
1224
+ }
1225
+ let cappedKey = key;
1226
+ if (key.length > budget.remaining) {
1227
+ cappedKey = key.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
1228
+ budget.remaining = 0;
1229
+ out[cappedKey] = TRUNCATION_MARKER2;
1230
+ break;
1231
+ }
1232
+ budget.remaining -= Math.max(key.length, 1);
1233
+ out[cappedKey] = boundedClone2(value[key], budget, depth + 1);
1234
+ }
1235
+ return out;
1236
+ }
1237
+ function boundedStringify(value, limit) {
1238
+ const max = limit != null ? limit : effectiveMaxTextFieldChars();
1239
+ try {
1240
+ if (typeof value === "string") {
1241
+ const text2 = JSON.stringify(capText2(value, max));
1242
+ return text2.length <= max ? text2 : truncateToLimit2(text2, max);
1243
+ }
1244
+ const pruned = boundedClone2(value, { remaining: max + TRUNCATION_MARKER2.length + 256 }, 0);
1245
+ const text = JSON.stringify(pruned);
1246
+ if (text === void 0) return void 0;
1247
+ return text.length <= max ? text : truncateToLimit2(text, max);
1248
+ } catch (e) {
1249
+ return void 0;
1250
+ }
1251
+ }
1252
+
660
1253
  // src/callback-handler.ts
661
1254
  var LANGGRAPH_INTERNAL_NAMES = /* @__PURE__ */ new Set([
662
1255
  "LangGraph",
@@ -746,7 +1339,7 @@ var RaindropCallbackHandler = class extends BaseCallbackHandler {
746
1339
  await this.eventShipper.patch(eventId, {
747
1340
  userId: this.userId,
748
1341
  convoId: this.convoId,
749
- input: prompts.join("\n"),
1342
+ input: capText2(prompts.join("\n")),
750
1343
  properties: buildProperties(tags, metadata),
751
1344
  isPending: true
752
1345
  });
@@ -776,7 +1369,7 @@ var RaindropCallbackHandler = class extends BaseCallbackHandler {
776
1369
  return role === "human";
777
1370
  });
778
1371
  const lastMsg = userMessages.length > 0 ? userMessages[userMessages.length - 1] : allMessages[allMessages.length - 1];
779
- const inputText = lastMsg ? typeof lastMsg.content === "string" ? lastMsg.content : safeStringify(lastMsg.content) : void 0;
1372
+ const inputText = lastMsg ? typeof lastMsg.content === "string" ? capText2(lastMsg.content) : safeStringify(lastMsg.content) : void 0;
780
1373
  await this.eventShipper.patch(eventId, {
781
1374
  userId: this.userId,
782
1375
  convoId: this.convoId,
@@ -806,7 +1399,7 @@ var RaindropCallbackHandler = class extends BaseCallbackHandler {
806
1399
  if (promptTokens != null) properties["ai.usage.prompt_tokens"] = promptTokens;
807
1400
  if (completionTokens != null) properties["ai.usage.completion_tokens"] = completionTokens;
808
1401
  await this.finalizeEventIfRoot(runId, {
809
- output: outputText,
1402
+ output: capText2(outputText),
810
1403
  model,
811
1404
  properties: Object.keys(properties).length > 0 ? properties : void 0
812
1405
  });
@@ -899,7 +1492,7 @@ var RaindropCallbackHandler = class extends BaseCallbackHandler {
899
1492
  try {
900
1493
  const span = this.spans.get(runId);
901
1494
  if (span) {
902
- const outputStr = typeof output === "string" ? output : safeStringify(output);
1495
+ const outputStr = typeof output === "string" ? output : safeStringify(output, 4096);
903
1496
  this.traceShipper.endSpan(span, {
904
1497
  attributes: [attrString("tool.output", truncate(outputStr, 4096))]
905
1498
  });
@@ -984,7 +1577,7 @@ var RaindropCallbackHandler = class extends BaseCallbackHandler {
984
1577
  attributes: [
985
1578
  attrString("agent.tool", action.tool),
986
1579
  attrString("agent.tool_input", truncate(
987
- typeof action.toolInput === "string" ? action.toolInput : safeStringify(action.toolInput),
1580
+ typeof action.toolInput === "string" ? action.toolInput : safeStringify(action.toolInput, 4096),
988
1581
  4096
989
1582
  ))
990
1583
  ]
@@ -1026,13 +1619,10 @@ function buildProperties(tags, metadata) {
1026
1619
  if (metadata) Object.assign(props, metadata);
1027
1620
  return props;
1028
1621
  }
1029
- function safeStringify(value) {
1622
+ function safeStringify(value, limit) {
1030
1623
  var _a;
1031
- try {
1032
- return (_a = JSON.stringify(value)) != null ? _a : "";
1033
- } catch (e) {
1034
- return String(value);
1035
- }
1624
+ if (value === void 0) return "";
1625
+ return (_a = boundedStringify(value, limit)) != null ? _a : String(value);
1036
1626
  }
1037
1627
  function truncate(str, maxLen) {
1038
1628
  if (str.length <= maxLen) return str;
@@ -1051,14 +1641,14 @@ function extractLLMMetadata(output) {
1051
1641
  const usage = (_g = usageMeta != null ? usageMeta : tokenUsage) != null ? _g : estimatedTokens;
1052
1642
  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;
1053
1643
  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;
1054
- 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;
1644
+ 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;
1055
1645
  return { model, promptTokens, completionTokens, outputText };
1056
1646
  }
1057
1647
 
1058
1648
  // package.json
1059
1649
  var package_default = {
1060
1650
  name: "@raindrop-ai/langchain",
1061
- version: "0.0.3",
1651
+ version: "0.0.5",
1062
1652
  description: "Raindrop integration for LangChain",
1063
1653
  main: "dist/index.js",
1064
1654
  module: "dist/index.mjs",
@@ -1132,6 +1722,7 @@ function createRaindropLangChain(opts) {
1132
1722
  var _a, _b;
1133
1723
  const writeKey = opts.writeKey;
1134
1724
  const enabled = !!writeKey;
1725
+ setMaxTextFieldChars(opts.maxTextFieldChars);
1135
1726
  if (!writeKey) {
1136
1727
  console.warn("[raindrop-ai/langchain] writeKey not provided; telemetry shipping is disabled");
1137
1728
  }
@@ -1140,6 +1731,7 @@ function createRaindropLangChain(opts) {
1140
1731
  endpoint: opts.endpoint,
1141
1732
  enabled,
1142
1733
  debug: (_a = opts.debug) != null ? _a : false,
1734
+ projectId: opts.projectId,
1143
1735
  sdkName: "langchain",
1144
1736
  libraryName,
1145
1737
  libraryVersion
@@ -1149,6 +1741,7 @@ function createRaindropLangChain(opts) {
1149
1741
  endpoint: opts.endpoint,
1150
1742
  enabled,
1151
1743
  debug: (_b = opts.debug) != null ? _b : false,
1744
+ projectId: opts.projectId,
1152
1745
  sdkName: "langchain",
1153
1746
  serviceName: "raindrop.langchain",
1154
1747
  serviceVersion: libraryVersion