@raindrop-ai/deep-agents 0.0.1 → 0.0.3

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-SK6EJEO7.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,112 @@ 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 PROJECT_ID_HEADER = "X-Raindrop-Project-Id";
384
+ var PROJECT_ID_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
385
+ function isValidProjectIdSlug(value) {
386
+ return PROJECT_ID_SLUG_PATTERN.test(value);
387
+ }
388
+ function normalizeProjectId(raw, opts) {
389
+ if (typeof raw !== "string") return void 0;
390
+ const trimmed = raw.trim();
391
+ if (!trimmed) return void 0;
392
+ if (!isValidProjectIdSlug(trimmed) && opts.debug) {
393
+ console.warn(
394
+ `${opts.prefix} projectId "${trimmed}" does not match slug ${PROJECT_ID_SLUG_PATTERN.source}; sending anyway \u2014 backend may reject with HTTP 400`
395
+ );
396
+ }
397
+ return trimmed;
398
+ }
399
+ function projectIdHeaders(projectId) {
400
+ return projectId ? { [PROJECT_ID_HEADER]: projectId } : {};
401
+ }
402
+ var SHUTDOWN_DEADLINE_MS = 1e4;
403
+ var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
232
404
  function mergePatches(target, source) {
233
405
  var _a, _b, _c, _d;
234
406
  const out = { ...target, ...source };
@@ -246,7 +418,8 @@ var EventShipper = class {
246
418
  this.sticky = /* @__PURE__ */ new Map();
247
419
  this.timers = /* @__PURE__ */ new Map();
248
420
  this.inFlight = /* @__PURE__ */ new Set();
249
- var _a, _b, _c, _d, _e, _f, _g;
421
+ this.hasShutdown = false;
422
+ var _a, _b, _c, _d, _e, _f, _g, _h;
250
423
  this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
251
424
  this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
252
425
  this.enabled = opts.enabled !== false;
@@ -255,11 +428,20 @@ var EventShipper = class {
255
428
  this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
256
429
  this.prefix = `[raindrop-ai/${this.sdkName}]`;
257
430
  this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
431
+ this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
432
+ this.localDebuggerUrl = (_f = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _f : void 0;
433
+ if (this.debug && this.localDebuggerUrl) {
434
+ console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
435
+ }
436
+ this.projectId = normalizeProjectId(opts.projectId, {
437
+ debug: this.debug,
438
+ prefix: this.prefix
439
+ });
258
440
  const isNode = typeof process !== "undefined" && typeof process.version === "string";
259
441
  this.context = {
260
442
  library: {
261
- name: (_f = opts.libraryName) != null ? _f : "@raindrop-ai/core",
262
- version: (_g = opts.libraryVersion) != null ? _g : "0.0.0"
443
+ name: (_g = opts.libraryName) != null ? _g : "@raindrop-ai/core",
444
+ version: (_h = opts.libraryVersion) != null ? _h : "0.0.0"
263
445
  },
264
446
  metadata: {
265
447
  jsRuntime: isNode ? "node" : "web",
@@ -273,10 +455,55 @@ var EventShipper = class {
273
455
  authHeaders() {
274
456
  return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
275
457
  }
458
+ requestHeaders() {
459
+ return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
460
+ }
461
+ /**
462
+ * Build the retry/timeout options for one POST, honoring the shutdown
463
+ * deadline. Returns `null` when the shutdown drain window is exhausted —
464
+ * the caller must drop the payload (with a rate-limited warning) instead
465
+ * of issuing a request that could outlive process exit.
466
+ *
467
+ * Checked fresh on EVERY send, so a shutdown that begins while the flush
468
+ * path is mid-drain takes effect immediately: no further retries, and the
469
+ * per-attempt timeout is clamped to the remaining window. After
470
+ * `shutdown()` returns (deadline cleared, `hasShutdown` still set),
471
+ * sends — late callers, or flush work the deadline abandoned mid-drain —
472
+ * run as a single short attempt rather than regaining the full retry
473
+ * schedule.
474
+ */
475
+ requestOpts() {
476
+ if (this.shutdownDeadlineAt !== void 0) {
477
+ const remainingMs = this.shutdownDeadlineAt - Date.now();
478
+ if (remainingMs <= 0) return null;
479
+ return {
480
+ maxAttempts: 1,
481
+ debug: this.debug,
482
+ sdkName: this.sdkName,
483
+ timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
484
+ };
485
+ }
486
+ if (this.hasShutdown) {
487
+ return {
488
+ maxAttempts: 1,
489
+ debug: this.debug,
490
+ sdkName: this.sdkName,
491
+ timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
492
+ };
493
+ }
494
+ return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
495
+ }
276
496
  async patch(eventId, patch) {
277
497
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
278
498
  if (!this.enabled) return;
279
499
  if (!eventId || !eventId.trim()) return;
500
+ const maxChars = resolveMaxTextFieldChars(this.maxTextFieldCharsOpt);
501
+ if (typeof patch.input === "string" && patch.input.length > maxChars) {
502
+ patch = { ...patch, input: capText(patch.input, maxChars) };
503
+ }
504
+ if (typeof patch.output === "string" && patch.output.length > maxChars) {
505
+ patch = { ...patch, output: capText(patch.output, maxChars) };
506
+ }
280
507
  if (this.debug) {
281
508
  console.log(`${this.prefix} queue patch`, {
282
509
  eventId,
@@ -323,9 +550,16 @@ var EventShipper = class {
323
550
  })));
324
551
  }
325
552
  async shutdown() {
326
- for (const t of this.timers.values()) clearTimeout(t);
327
- this.timers.clear();
328
- await this.flush();
553
+ this.hasShutdown = true;
554
+ this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
555
+ try {
556
+ for (const t of this.timers.values()) clearTimeout(t);
557
+ this.timers.clear();
558
+ const settled = await raceWithTimeout(this.flush(), SHUTDOWN_DEADLINE_MS);
559
+ if (!settled) this.warnShutdownDrop("in-flight request(s) at shutdown");
560
+ } finally {
561
+ this.shutdownDeadlineAt = void 0;
562
+ }
329
563
  }
330
564
  async trackSignal(signal) {
331
565
  var _a, _b;
@@ -345,16 +579,21 @@ var EventShipper = class {
345
579
  }
346
580
  }
347
581
  ];
582
+ if (!this.writeKey) return;
348
583
  const url = `${this.baseUrl}signals/track`;
584
+ const opts = this.requestOpts();
585
+ if (!opts) {
586
+ this.warnShutdownDrop("signal");
587
+ return;
588
+ }
349
589
  try {
350
- await postJson(url, body, this.authHeaders(), {
351
- maxAttempts: 3,
352
- debug: this.debug,
353
- sdkName: this.sdkName
354
- });
590
+ await postJson(url, body, this.requestHeaders(), opts);
355
591
  } catch (err) {
356
592
  const msg = err instanceof Error ? err.message : String(err);
357
- console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`);
593
+ rateLimitedLog(
594
+ `${this.prefix}.send_signal_failed`,
595
+ () => console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`)
596
+ );
358
597
  }
359
598
  }
360
599
  async identify(users) {
@@ -375,19 +614,30 @@ var EventShipper = class {
375
614
  traits: (_a = user.traits) != null ? _a : {}
376
615
  };
377
616
  });
617
+ if (!this.writeKey) return;
378
618
  if (body.length === 0) return;
379
619
  const url = `${this.baseUrl}users/identify`;
620
+ const opts = this.requestOpts();
621
+ if (!opts) {
622
+ this.warnShutdownDrop("identify");
623
+ return;
624
+ }
380
625
  try {
381
- await postJson(url, body, this.authHeaders(), {
382
- maxAttempts: 3,
383
- debug: this.debug,
384
- sdkName: this.sdkName
385
- });
626
+ await postJson(url, body, this.requestHeaders(), opts);
386
627
  } catch (err) {
387
628
  const msg = err instanceof Error ? err.message : String(err);
388
- console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`);
629
+ rateLimitedLog(
630
+ `${this.prefix}.send_identify_failed`,
631
+ () => console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`)
632
+ );
389
633
  }
390
634
  }
635
+ warnShutdownDrop(what) {
636
+ rateLimitedLog(
637
+ `${this.prefix}.shutdown_deadline`,
638
+ () => console.warn(`${this.prefix} shutdown flush deadline exceeded; dropping ${what}`)
639
+ );
640
+ }
391
641
  async flushOne(eventId) {
392
642
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
393
643
  if (!this.enabled) return;
@@ -451,11 +701,25 @@ var EventShipper = class {
451
701
  endpoint: url
452
702
  });
453
703
  }
454
- const p = postJson(url, payload, this.authHeaders(), {
455
- maxAttempts: 3,
456
- debug: this.debug,
457
- sdkName: this.sdkName
458
- });
704
+ if (this.localDebuggerUrl) {
705
+ mirrorPartialEventToLocalDebugger(payload, {
706
+ baseUrl: this.localDebuggerUrl,
707
+ writeKey: this.writeKey,
708
+ debug: this.debug,
709
+ sdkName: this.sdkName
710
+ });
711
+ }
712
+ if (!this.writeKey) {
713
+ if (!isPending) this.sticky.delete(eventId);
714
+ return;
715
+ }
716
+ const opts = this.requestOpts();
717
+ if (!opts) {
718
+ this.warnShutdownDrop(`track_partial ${eventId}`);
719
+ if (!isPending) this.sticky.delete(eventId);
720
+ return;
721
+ }
722
+ const p = postJson(url, payload, this.requestHeaders(), opts);
459
723
  this.inFlight.add(p);
460
724
  try {
461
725
  try {
@@ -465,7 +729,10 @@ var EventShipper = class {
465
729
  }
466
730
  } catch (err) {
467
731
  const msg = err instanceof Error ? err.message : String(err);
468
- console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`);
732
+ rateLimitedLog(
733
+ `${this.prefix}.send_track_partial_failed`,
734
+ () => console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`)
735
+ );
469
736
  }
470
737
  } finally {
471
738
  this.inFlight.delete(p);
@@ -475,28 +742,114 @@ var EventShipper = class {
475
742
  }
476
743
  }
477
744
  };
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;
745
+ var DEFAULT_SECRET_KEY_NAMES = [
746
+ "apikey",
747
+ "apisecret",
748
+ "apitoken",
749
+ "secretaccesskey",
750
+ "sessiontoken",
751
+ "privatekey",
752
+ "privatekeyid",
753
+ "clientsecret",
754
+ "accesstoken",
755
+ "refreshtoken",
756
+ "oauthtoken",
757
+ "bearertoken",
758
+ "authorization",
759
+ "password",
760
+ "passphrase"
761
+ ];
762
+ var REDACTED_PLACEHOLDER = "[REDACTED]";
763
+ function normalizeKeyName(name) {
764
+ return name.toLowerCase().replace(/[-_.]/g, "");
483
765
  }
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
- });
766
+ function redactSecretsInObject(value, options) {
767
+ var _a, _b;
768
+ const normalizedSecretSet = buildSecretSet((_a = options == null ? void 0 : options.secretKeyNames) != null ? _a : DEFAULT_SECRET_KEY_NAMES);
769
+ const placeholder = (_b = options == null ? void 0 : options.placeholder) != null ? _b : REDACTED_PLACEHOLDER;
770
+ const seen = /* @__PURE__ */ new WeakSet();
771
+ const walk = (node) => {
772
+ if (node === null || typeof node !== "object") return node;
773
+ if (seen.has(node)) return "[CIRCULAR]";
774
+ seen.add(node);
775
+ if (Array.isArray(node)) {
776
+ return node.map((item) => walk(item));
777
+ }
778
+ const out = {};
779
+ for (const [k, v] of Object.entries(node)) {
780
+ if (normalizedSecretSet.has(normalizeKeyName(k))) {
781
+ out[k] = placeholder;
782
+ } else {
783
+ out[k] = walk(v);
784
+ }
785
+ }
786
+ return out;
787
+ };
788
+ return walk(value);
789
+ }
790
+ function buildSecretSet(names) {
791
+ const set = /* @__PURE__ */ new Set();
792
+ for (const name of names) set.add(normalizeKeyName(name));
793
+ return set;
794
+ }
795
+ var DEFAULT_REDACT_ATTRIBUTE_KEYS = [
796
+ "ai.request.providerOptions",
797
+ "ai.response.providerMetadata"
798
+ ];
799
+ function defaultTransformSpan(span) {
800
+ const attrs = span.attributes;
801
+ if (!attrs || attrs.length === 0) return span;
802
+ let nextAttrs;
803
+ for (let i = 0; i < attrs.length; i++) {
804
+ const attr = attrs[i];
805
+ const redacted = redactJsonAttributeValue(attr.key, attr.value);
806
+ if (redacted === void 0) continue;
807
+ if (!nextAttrs) nextAttrs = attrs.slice();
808
+ nextAttrs[i] = { key: attr.key, value: redacted };
809
+ }
810
+ if (!nextAttrs) return span;
811
+ return { ...span, attributes: nextAttrs };
812
+ }
813
+ var REDACT_JSON_ATTRIBUTE_KEYS = new Set(DEFAULT_REDACT_ATTRIBUTE_KEYS);
814
+ function redactJsonAttributeValue(key, value) {
815
+ if (!REDACT_JSON_ATTRIBUTE_KEYS.has(key)) return void 0;
816
+ const json = value.stringValue;
817
+ if (typeof json !== "string" || json.length === 0) return void 0;
818
+ let parsed;
819
+ try {
820
+ parsed = JSON.parse(json);
821
+ } catch (e) {
822
+ return void 0;
823
+ }
824
+ const scrubbed = redactSecretsInObject(parsed);
825
+ let scrubbedJson;
826
+ try {
827
+ scrubbedJson = JSON.stringify(scrubbed);
828
+ } catch (e) {
829
+ return void 0;
830
+ }
831
+ if (scrubbedJson === json) return void 0;
832
+ return { stringValue: scrubbedJson };
833
+ }
834
+ function applyOtelSpanAttributeLimit(limit) {
835
+ var _a, _b;
836
+ try {
837
+ 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;
838
+ if (!raw) return limit;
839
+ const parsed = Number.parseInt(raw, 10);
840
+ if (Number.isFinite(parsed) && parsed > 0) {
841
+ return Math.min(limit, parsed);
842
+ }
843
+ } catch (e) {
844
+ }
845
+ return limit;
494
846
  }
495
847
  var TraceShipper = class {
496
848
  constructor(opts) {
497
849
  this.queue = [];
498
850
  this.inFlight = /* @__PURE__ */ new Set();
499
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
851
+ this.hasShutdown = false;
852
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
500
853
  this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
501
854
  this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
502
855
  this.enabled = opts.enabled !== false;
@@ -509,13 +862,83 @@ var TraceShipper = class {
509
862
  this.prefix = `[raindrop-ai/${this.sdkName}]`;
510
863
  this.serviceName = (_g = opts.serviceName) != null ? _g : "raindrop.core";
511
864
  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}`);
865
+ this.localDebuggerUrl = (_i = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _i : void 0;
866
+ if (this.debug && this.localDebuggerUrl) {
867
+ console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
868
+ }
869
+ this.projectId = normalizeProjectId(opts.projectId, {
870
+ debug: this.debug,
871
+ prefix: this.prefix
872
+ });
873
+ this.transformSpanHook = opts.transformSpan;
874
+ this.disableDefaultRedaction = opts.disableDefaultRedaction === true;
875
+ this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
876
+ }
877
+ /**
878
+ * Cap every string attribute value on the span. O(#attributes) length
879
+ * checks; only oversized values pay a slice. Runs AFTER the redaction
880
+ * pipeline so the default secret-scrub still sees parseable JSON in
881
+ * `ai.request.providerOptions` / `ai.response.providerMetadata` (capping
882
+ * first could cut a JSON blob mid-way, fail the parse, and ship secrets
883
+ * in the surviving prefix).
884
+ *
885
+ * A stricter `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var is honored
886
+ * for span content, matching the Python SDK and the OTel SDK convention.
887
+ */
888
+ capSpanAttributes(span) {
889
+ var _a;
890
+ const maxChars = applyOtelSpanAttributeLimit(
891
+ resolveMaxTextFieldChars(this.maxTextFieldCharsOpt)
892
+ );
893
+ const attrs = span.attributes;
894
+ if (!attrs || attrs.length === 0) return span;
895
+ let nextAttrs;
896
+ for (let i = 0; i < attrs.length; i++) {
897
+ const attr = attrs[i];
898
+ const value = (_a = attr.value) == null ? void 0 : _a.stringValue;
899
+ if (typeof value !== "string" || value.length <= maxChars) continue;
900
+ if (!nextAttrs) nextAttrs = attrs.slice();
901
+ nextAttrs[i] = {
902
+ key: attr.key,
903
+ value: { ...attr.value, stringValue: capText(value, maxChars) }
904
+ };
905
+ }
906
+ if (!nextAttrs) return span;
907
+ return { ...span, attributes: nextAttrs };
908
+ }
909
+ /**
910
+ * Apply the user `transformSpan` hook (if any) followed by the default
911
+ * redactor (unless disabled). Returns either the (possibly new) span to
912
+ * ship, or `null` to drop the span entirely.
913
+ *
914
+ * Ordering: user hook runs first so callers can rewrite the span freely
915
+ * (rename attrs, add new ones, scrub things the default doesn't know
916
+ * about). The default redactor then runs on whatever the user produced,
917
+ * acting as the always-on floor for documented BYOK secrets. If the user
918
+ * sets `disableDefaultRedaction: true`, the floor is skipped.
919
+ *
920
+ * Fail-closed: if the user hook throws, the span is dropped — a buggy
921
+ * hook can never accidentally ship raw, un-redacted spans.
922
+ */
923
+ redactSpan(span) {
924
+ let current = span;
925
+ if (this.transformSpanHook) {
926
+ try {
927
+ const result = this.transformSpanHook(current);
928
+ if (result === null) return null;
929
+ if (result !== void 0) current = result;
930
+ } catch (err) {
931
+ if (this.debug) {
932
+ const msg = err instanceof Error ? err.message : String(err);
933
+ console.warn(`${this.prefix} transformSpan hook threw: ${msg}`);
934
+ }
935
+ return null;
517
936
  }
518
937
  }
938
+ if (!this.disableDefaultRedaction) {
939
+ current = defaultTransformSpan(current);
940
+ }
941
+ return this.capSpanAttributes(current);
519
942
  }
520
943
  isDebugEnabled() {
521
944
  return this.debug;
@@ -523,6 +946,9 @@ var TraceShipper = class {
523
946
  authHeaders() {
524
947
  return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
525
948
  }
949
+ requestHeaders() {
950
+ return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
951
+ }
526
952
  startSpan(args) {
527
953
  var _a, _b;
528
954
  const ids = createSpanIds(args.parent);
@@ -533,8 +959,8 @@ var TraceShipper = class {
533
959
  ];
534
960
  if ((_b = args.attributes) == null ? void 0 : _b.length) attrs.push(...args.attributes);
535
961
  const span = { ids, name: args.name, startTimeUnixNano: started, attributes: attrs };
536
- if (this.localDebuggerUrl) {
537
- const openSpan = buildOtlpSpan({
962
+ this.mirrorToLocalDebugger(
963
+ buildOtlpSpan({
538
964
  ids: span.ids,
539
965
  name: span.name,
540
966
  startTimeUnixNano: span.startTimeUnixNano,
@@ -542,16 +968,21 @@ var TraceShipper = class {
542
968
  // placeholder — will be updated on endSpan
543
969
  attributes: span.attributes,
544
970
  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
- }
971
+ })
972
+ );
553
973
  return span;
554
974
  }
975
+ mirrorToLocalDebugger(span) {
976
+ if (!this.localDebuggerUrl) return;
977
+ const redacted = this.redactSpan(span);
978
+ if (redacted === null) return;
979
+ const body = buildExportTraceServiceRequest([redacted], this.serviceName, this.serviceVersion);
980
+ mirrorTraceExportToLocalDebugger(body, {
981
+ baseUrl: this.localDebuggerUrl,
982
+ debug: false,
983
+ sdkName: this.sdkName
984
+ });
985
+ }
555
986
  endSpan(span, extra) {
556
987
  var _a, _b;
557
988
  if (span.endTimeUnixNano) return;
@@ -573,14 +1004,7 @@ var TraceShipper = class {
573
1004
  status
574
1005
  });
575
1006
  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
- }
1007
+ this.mirrorToLocalDebugger(otlp);
584
1008
  }
585
1009
  createSpan(args) {
586
1010
  var _a;
@@ -598,14 +1022,7 @@ var TraceShipper = class {
598
1022
  status: args.status
599
1023
  });
600
1024
  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
- }
1025
+ this.mirrorToLocalDebugger(otlp);
609
1026
  }
610
1027
  enqueue(span) {
611
1028
  if (!this.enabled) return;
@@ -617,10 +1034,12 @@ var TraceShipper = class {
617
1034
  )}`
618
1035
  );
619
1036
  }
1037
+ const redacted = this.redactSpan(span);
1038
+ if (redacted === null) return;
620
1039
  if (this.queue.length >= this.maxQueueSize) {
621
1040
  this.queue.shift();
622
1041
  }
623
- this.queue.push(span);
1042
+ this.queue.push(redacted);
624
1043
  if (this.queue.length >= this.maxBatchSize) {
625
1044
  void this.flush().catch(() => {
626
1045
  });
@@ -642,6 +1061,17 @@ var TraceShipper = class {
642
1061
  }
643
1062
  while (this.queue.length > 0) {
644
1063
  const batch = this.queue.splice(0, this.maxBatchSize);
1064
+ if (!this.writeKey) continue;
1065
+ const opts = this.requestOpts();
1066
+ if (!opts) {
1067
+ rateLimitedLog(
1068
+ `${this.prefix}.shutdown_deadline`,
1069
+ () => console.warn(
1070
+ `${this.prefix} shutdown flush deadline exceeded; dropping ${batch.length} spans`
1071
+ )
1072
+ );
1073
+ continue;
1074
+ }
645
1075
  const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
646
1076
  const url = `${this.baseUrl}traces`;
647
1077
  if (this.debug) {
@@ -650,11 +1080,7 @@ var TraceShipper = class {
650
1080
  endpoint: url
651
1081
  });
652
1082
  }
653
- const p = postJson(url, body, this.authHeaders(), {
654
- maxAttempts: 3,
655
- debug: this.debug,
656
- sdkName: this.sdkName
657
- });
1083
+ const p = postJson(url, body, this.requestHeaders(), opts);
658
1084
  this.inFlight.add(p);
659
1085
  try {
660
1086
  try {
@@ -662,21 +1088,61 @@ var TraceShipper = class {
662
1088
  if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
663
1089
  } catch (err) {
664
1090
  const msg = err instanceof Error ? err.message : String(err);
665
- console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`);
1091
+ rateLimitedLog(
1092
+ `${this.prefix}.send_spans_failed`,
1093
+ () => console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`)
1094
+ );
666
1095
  }
667
1096
  } finally {
668
1097
  this.inFlight.delete(p);
669
1098
  }
670
1099
  }
671
1100
  }
1101
+ /** See EventShipper.requestOpts — same shutdown-budget semantics. */
1102
+ requestOpts() {
1103
+ if (this.shutdownDeadlineAt !== void 0) {
1104
+ const remainingMs = this.shutdownDeadlineAt - Date.now();
1105
+ if (remainingMs <= 0) return null;
1106
+ return {
1107
+ maxAttempts: 1,
1108
+ debug: this.debug,
1109
+ sdkName: this.sdkName,
1110
+ timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
1111
+ };
1112
+ }
1113
+ if (this.hasShutdown) {
1114
+ return {
1115
+ maxAttempts: 1,
1116
+ debug: this.debug,
1117
+ sdkName: this.sdkName,
1118
+ timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
1119
+ };
1120
+ }
1121
+ return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
1122
+ }
672
1123
  async shutdown() {
673
- if (this.timer) {
674
- clearTimeout(this.timer);
675
- this.timer = void 0;
1124
+ this.hasShutdown = true;
1125
+ this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
1126
+ try {
1127
+ if (this.timer) {
1128
+ clearTimeout(this.timer);
1129
+ this.timer = void 0;
1130
+ }
1131
+ const drain = async () => {
1132
+ await this.flush();
1133
+ await Promise.all([...this.inFlight].map((p) => p.catch(() => {
1134
+ })));
1135
+ };
1136
+ const settled = await raceWithTimeout(drain(), SHUTDOWN_DEADLINE_MS);
1137
+ if (!settled) {
1138
+ rateLimitedLog(
1139
+ `${this.prefix}.shutdown_deadline`,
1140
+ () => console.warn(`${this.prefix} shutdown flush deadline exceeded; abandoning in-flight spans`)
1141
+ );
1142
+ }
1143
+ } finally {
1144
+ this.shutdownDeadlineAt = void 0;
676
1145
  }
677
- await this.flush();
678
- await Promise.all([...this.inFlight].map((p) => p.catch(() => {
679
- })));
680
1146
  }
681
1147
  };
682
1148
 
@@ -711,13 +1177,13 @@ function extractTextFromContent(content) {
711
1177
  }
712
1178
  }
713
1179
  if (parts.length > 0) return parts.join("\n");
714
- return safeStringify(content);
1180
+ return safeStringify(content, CHAIN_TEXT_MAX_LEN);
715
1181
  }
716
1182
  if (typeof content === "object") {
717
1183
  const c = content;
718
1184
  if (typeof c.text === "string") return c.text;
719
1185
  if (typeof c.content === "string") return c.content;
720
- return safeStringify(content);
1186
+ return safeStringify(content, CHAIN_TEXT_MAX_LEN);
721
1187
  }
722
1188
  return String(content);
723
1189
  }
@@ -1016,7 +1482,7 @@ var RaindropDeepAgentsHandler = class extends import_base.BaseCallbackHandler {
1016
1482
  try {
1017
1483
  const span = this.spans.get(runId);
1018
1484
  if (span) {
1019
- const outputStr = typeof output === "string" ? output : safeStringify(output);
1485
+ const outputStr = typeof output === "string" ? output : safeStringify(output, 4096);
1020
1486
  this.traceShipper.endSpan(span, {
1021
1487
  attributes: [attrString("tool.output", truncate(outputStr, 4096))]
1022
1488
  });
@@ -1062,7 +1528,7 @@ var RaindropDeepAgentsHandler = class extends import_base.BaseCallbackHandler {
1062
1528
  attributes: [
1063
1529
  attrString("ai.toolCall.name", action.tool),
1064
1530
  attrString("agent.tool_input", truncate(
1065
- typeof action.toolInput === "string" ? action.toolInput : safeStringify(action.toolInput),
1531
+ typeof action.toolInput === "string" ? action.toolInput : safeStringify(action.toolInput, 4096),
1066
1532
  4096
1067
1533
  ))
1068
1534
  ]
@@ -1097,11 +1563,68 @@ function buildProperties(tags, metadata) {
1097
1563
  if (metadata) Object.assign(props, metadata);
1098
1564
  return props;
1099
1565
  }
1100
- function safeStringify(value) {
1566
+ var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
1567
+ var MAX_TEXT_FIELD_CHARS = 1e6;
1568
+ var MAX_BOUNDED_DEPTH = 12;
1569
+ function boundedClone2(value, budget, depth) {
1570
+ if (budget.remaining <= 0) return TRUNCATION_MARKER2;
1571
+ if (typeof value === "string") {
1572
+ if (value.length > budget.remaining) {
1573
+ const taken = value.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
1574
+ budget.remaining = 0;
1575
+ return taken;
1576
+ }
1577
+ budget.remaining -= Math.max(value.length, 1);
1578
+ return value;
1579
+ }
1580
+ if (value === null || typeof value === "number" || typeof value === "boolean") {
1581
+ budget.remaining -= 8;
1582
+ return value;
1583
+ }
1584
+ if (typeof value !== "object") {
1585
+ budget.remaining -= 8;
1586
+ return value;
1587
+ }
1588
+ if (depth >= MAX_BOUNDED_DEPTH) {
1589
+ budget.remaining -= 16;
1590
+ return `<max depth: ${TRUNCATION_MARKER2}>`;
1591
+ }
1592
+ const withToJson = value;
1593
+ if (typeof withToJson.toJSON === "function") {
1594
+ try {
1595
+ return boundedClone2(withToJson.toJSON(), budget, depth + 1);
1596
+ } catch {
1597
+ }
1598
+ }
1599
+ if (Array.isArray(value)) {
1600
+ const out2 = [];
1601
+ for (const item of value) {
1602
+ if (budget.remaining <= 0) {
1603
+ out2.push(TRUNCATION_MARKER2);
1604
+ break;
1605
+ }
1606
+ out2.push(boundedClone2(item, budget, depth + 1));
1607
+ }
1608
+ return out2;
1609
+ }
1610
+ const out = {};
1611
+ for (const key of Object.keys(value)) {
1612
+ if (budget.remaining <= 0) {
1613
+ out["..."] = TRUNCATION_MARKER2;
1614
+ break;
1615
+ }
1616
+ budget.remaining -= Math.max(key.length, 1);
1617
+ out[key] = boundedClone2(value[key], budget, depth + 1);
1618
+ }
1619
+ return out;
1620
+ }
1621
+ function safeStringify(value, limit = MAX_TEXT_FIELD_CHARS) {
1101
1622
  try {
1102
- return JSON.stringify(value) ?? "";
1623
+ const pruned = boundedClone2(value, { remaining: limit + TRUNCATION_MARKER2.length + 256 }, 0);
1624
+ return JSON.stringify(pruned) ?? "";
1103
1625
  } catch {
1104
- return String(value);
1626
+ const fallback = String(value);
1627
+ return fallback.length > limit ? fallback.slice(0, limit) : fallback;
1105
1628
  }
1106
1629
  }
1107
1630
  function truncate(str, maxLen) {
@@ -1181,6 +1704,7 @@ function createRaindropDeepAgents(opts) {
1181
1704
  endpoint: opts.endpoint,
1182
1705
  enabled,
1183
1706
  debug: opts.debug ?? false,
1707
+ projectId: opts.projectId,
1184
1708
  sdkName: "deep-agents",
1185
1709
  libraryName,
1186
1710
  libraryVersion
@@ -1190,6 +1714,7 @@ function createRaindropDeepAgents(opts) {
1190
1714
  endpoint: opts.endpoint,
1191
1715
  enabled,
1192
1716
  debug: opts.debug ?? false,
1717
+ projectId: opts.projectId,
1193
1718
  sdkName: "deep-agents",
1194
1719
  serviceName: "raindrop.deep-agents",
1195
1720
  serviceVersion: libraryVersion