@raindrop-ai/pi-agent 0.0.4 → 0.0.6

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.cjs CHANGED
@@ -24,7 +24,7 @@ __export(index_exports, {
24
24
  });
25
25
  module.exports = __toCommonJS(index_exports);
26
26
 
27
- // ../core/dist/chunk-VUNUOE2X.js
27
+ // ../core/dist/chunk-SK6EJEO7.js
28
28
  function getCrypto() {
29
29
  const c = globalThis.crypto;
30
30
  return c;
@@ -79,6 +79,8 @@ function base64Encode(bytes) {
79
79
  }
80
80
  return out;
81
81
  }
82
+ var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
83
+ var MAX_RETRY_DELAY_MS = 3e4;
82
84
  function wait(ms) {
83
85
  return new Promise((resolve) => setTimeout(resolve, ms));
84
86
  }
@@ -86,6 +88,46 @@ function formatEndpoint(endpoint) {
86
88
  if (!endpoint) return void 0;
87
89
  return endpoint.endsWith("/") ? endpoint : `${endpoint}/`;
88
90
  }
91
+ function redactUrlForLog(url) {
92
+ try {
93
+ const parsed = new URL(url);
94
+ parsed.username = "";
95
+ parsed.password = "";
96
+ parsed.search = "";
97
+ parsed.hash = "";
98
+ return parsed.toString();
99
+ } catch (e) {
100
+ return "<unparseable-url>";
101
+ }
102
+ }
103
+ var RATE_LIMITED_LOG_INTERVAL_MS = 3e4;
104
+ var rateLimitedLogLast = /* @__PURE__ */ new Map();
105
+ function rateLimitedLog(key, log) {
106
+ const now = Date.now();
107
+ const last = rateLimitedLogLast.get(key);
108
+ if (last !== void 0 && now - last < RATE_LIMITED_LOG_INTERVAL_MS) {
109
+ return false;
110
+ }
111
+ rateLimitedLogLast.set(key, now);
112
+ log();
113
+ return true;
114
+ }
115
+ async function raceWithTimeout(promise, timeoutMs) {
116
+ let timer;
117
+ const settledInTime = await Promise.race([
118
+ promise.then(
119
+ () => true,
120
+ () => true
121
+ ),
122
+ new Promise((resolve) => {
123
+ var _a;
124
+ timer = setTimeout(() => resolve(false), Math.max(0, timeoutMs));
125
+ (_a = timer.unref) == null ? void 0 : _a.call(timer);
126
+ })
127
+ ]);
128
+ if (timer) clearTimeout(timer);
129
+ return settledInTime;
130
+ }
89
131
  function parseRetryAfter(headers) {
90
132
  var _a;
91
133
  const value = (_a = headers.get("Retry-After")) != null ? _a : headers.get("retry-after");
@@ -102,12 +144,12 @@ function parseRetryAfter(headers) {
102
144
  function getRetryDelayMs(attemptNumber, previousError) {
103
145
  if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
104
146
  const v = previousError.retryAfterMs;
105
- if (typeof v === "number") return Math.max(0, v);
147
+ if (typeof v === "number") return Math.min(Math.max(0, v), MAX_RETRY_DELAY_MS);
106
148
  }
107
149
  if (attemptNumber <= 1) return 0;
108
150
  const base = 500;
109
151
  const factor = Math.pow(2, attemptNumber - 2);
110
- return base * factor;
152
+ return Math.min(base * factor, MAX_RETRY_DELAY_MS);
111
153
  }
112
154
  async function withRetry(operation, opName, opts) {
113
155
  const prefix = opts.sdkName ? `[raindrop-ai/${opts.sdkName}]` : "[raindrop-ai/core]";
@@ -142,7 +184,9 @@ async function withRetry(operation, opName, opts) {
142
184
  throw lastError instanceof Error ? lastError : new Error(String(lastError));
143
185
  }
144
186
  async function postJson(url, body, headers, opts) {
145
- const opName = `POST ${url}`;
187
+ var _a;
188
+ const opName = `POST ${redactUrlForLog(url)}`;
189
+ const timeoutMs = (_a = opts.timeoutMs) != null ? _a : DEFAULT_REQUEST_TIMEOUT_MS;
146
190
  await withRetry(
147
191
  async () => {
148
192
  const resp = await fetch(url, {
@@ -151,7 +195,8 @@ async function postJson(url, body, headers, opts) {
151
195
  "Content-Type": "application/json",
152
196
  ...headers
153
197
  },
154
- body: JSON.stringify(body)
198
+ body: JSON.stringify(body),
199
+ signal: AbortSignal.timeout(timeoutMs)
155
200
  });
156
201
  if (!resp.ok) {
157
202
  const text = await resp.text().catch(() => "");
@@ -168,6 +213,27 @@ async function postJson(url, body, headers, opts) {
168
213
  opts
169
214
  );
170
215
  }
216
+ var DEFAULT_MAX_TEXT_FIELD_CHARS = 1e6;
217
+ var TRUNCATION_MARKER = "...[truncated by raindrop]";
218
+ var currentDefaultMaxTextFieldChars = DEFAULT_MAX_TEXT_FIELD_CHARS;
219
+ function resolveMaxTextFieldChars(value) {
220
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
221
+ return Math.floor(value);
222
+ }
223
+ return currentDefaultMaxTextFieldChars;
224
+ }
225
+ function truncateToLimit(text, limit) {
226
+ if (limit > TRUNCATION_MARKER.length) {
227
+ return text.slice(0, limit - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;
228
+ }
229
+ return text.slice(0, Math.max(0, limit));
230
+ }
231
+ function capText(value, limit) {
232
+ if (typeof value !== "string") return value;
233
+ const max = limit != null ? limit : currentDefaultMaxTextFieldChars;
234
+ if (value.length <= max) return value;
235
+ return truncateToLimit(value, max);
236
+ }
171
237
  var SpanStatusCode = {
172
238
  UNSET: 0,
173
239
  OK: 1,
@@ -310,6 +376,27 @@ function mirrorPartialEventToLocalDebugger(event, options = {}) {
310
376
  }).catch(() => {
311
377
  });
312
378
  }
379
+ var PROJECT_ID_HEADER = "X-Raindrop-Project-Id";
380
+ var PROJECT_ID_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
381
+ function isValidProjectIdSlug(value) {
382
+ return PROJECT_ID_SLUG_PATTERN.test(value);
383
+ }
384
+ function normalizeProjectId(raw, opts) {
385
+ if (typeof raw !== "string") return void 0;
386
+ const trimmed = raw.trim();
387
+ if (!trimmed) return void 0;
388
+ if (!isValidProjectIdSlug(trimmed) && opts.debug) {
389
+ console.warn(
390
+ `${opts.prefix} projectId "${trimmed}" does not match slug ${PROJECT_ID_SLUG_PATTERN.source}; sending anyway \u2014 backend may reject with HTTP 400`
391
+ );
392
+ }
393
+ return trimmed;
394
+ }
395
+ function projectIdHeaders(projectId) {
396
+ return projectId ? { [PROJECT_ID_HEADER]: projectId } : {};
397
+ }
398
+ var SHUTDOWN_DEADLINE_MS = 1e4;
399
+ var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
313
400
  function mergePatches(target, source) {
314
401
  var _a, _b, _c, _d;
315
402
  const out = { ...target, ...source };
@@ -327,6 +414,7 @@ var EventShipper = class {
327
414
  this.sticky = /* @__PURE__ */ new Map();
328
415
  this.timers = /* @__PURE__ */ new Map();
329
416
  this.inFlight = /* @__PURE__ */ new Set();
417
+ this.hasShutdown = false;
330
418
  var _a, _b, _c, _d, _e, _f, _g, _h;
331
419
  this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
332
420
  this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
@@ -336,10 +424,15 @@ var EventShipper = class {
336
424
  this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
337
425
  this.prefix = `[raindrop-ai/${this.sdkName}]`;
338
426
  this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
427
+ this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
339
428
  this.localDebuggerUrl = (_f = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _f : void 0;
340
429
  if (this.debug && this.localDebuggerUrl) {
341
430
  console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
342
431
  }
432
+ this.projectId = normalizeProjectId(opts.projectId, {
433
+ debug: this.debug,
434
+ prefix: this.prefix
435
+ });
343
436
  const isNode = typeof process !== "undefined" && typeof process.version === "string";
344
437
  this.context = {
345
438
  library: {
@@ -358,10 +451,55 @@ var EventShipper = class {
358
451
  authHeaders() {
359
452
  return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
360
453
  }
454
+ requestHeaders() {
455
+ return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
456
+ }
457
+ /**
458
+ * Build the retry/timeout options for one POST, honoring the shutdown
459
+ * deadline. Returns `null` when the shutdown drain window is exhausted —
460
+ * the caller must drop the payload (with a rate-limited warning) instead
461
+ * of issuing a request that could outlive process exit.
462
+ *
463
+ * Checked fresh on EVERY send, so a shutdown that begins while the flush
464
+ * path is mid-drain takes effect immediately: no further retries, and the
465
+ * per-attempt timeout is clamped to the remaining window. After
466
+ * `shutdown()` returns (deadline cleared, `hasShutdown` still set),
467
+ * sends — late callers, or flush work the deadline abandoned mid-drain —
468
+ * run as a single short attempt rather than regaining the full retry
469
+ * schedule.
470
+ */
471
+ requestOpts() {
472
+ if (this.shutdownDeadlineAt !== void 0) {
473
+ const remainingMs = this.shutdownDeadlineAt - Date.now();
474
+ if (remainingMs <= 0) return null;
475
+ return {
476
+ maxAttempts: 1,
477
+ debug: this.debug,
478
+ sdkName: this.sdkName,
479
+ timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
480
+ };
481
+ }
482
+ if (this.hasShutdown) {
483
+ return {
484
+ maxAttempts: 1,
485
+ debug: this.debug,
486
+ sdkName: this.sdkName,
487
+ timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
488
+ };
489
+ }
490
+ return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
491
+ }
361
492
  async patch(eventId, patch) {
362
493
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
363
494
  if (!this.enabled) return;
364
495
  if (!eventId || !eventId.trim()) return;
496
+ const maxChars = resolveMaxTextFieldChars(this.maxTextFieldCharsOpt);
497
+ if (typeof patch.input === "string" && patch.input.length > maxChars) {
498
+ patch = { ...patch, input: capText(patch.input, maxChars) };
499
+ }
500
+ if (typeof patch.output === "string" && patch.output.length > maxChars) {
501
+ patch = { ...patch, output: capText(patch.output, maxChars) };
502
+ }
365
503
  if (this.debug) {
366
504
  console.log(`${this.prefix} queue patch`, {
367
505
  eventId,
@@ -408,9 +546,16 @@ var EventShipper = class {
408
546
  })));
409
547
  }
410
548
  async shutdown() {
411
- for (const t of this.timers.values()) clearTimeout(t);
412
- this.timers.clear();
413
- await this.flush();
549
+ this.hasShutdown = true;
550
+ this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
551
+ try {
552
+ for (const t of this.timers.values()) clearTimeout(t);
553
+ this.timers.clear();
554
+ const settled = await raceWithTimeout(this.flush(), SHUTDOWN_DEADLINE_MS);
555
+ if (!settled) this.warnShutdownDrop("in-flight request(s) at shutdown");
556
+ } finally {
557
+ this.shutdownDeadlineAt = void 0;
558
+ }
414
559
  }
415
560
  async trackSignal(signal) {
416
561
  var _a, _b;
@@ -432,15 +577,19 @@ var EventShipper = class {
432
577
  ];
433
578
  if (!this.writeKey) return;
434
579
  const url = `${this.baseUrl}signals/track`;
580
+ const opts = this.requestOpts();
581
+ if (!opts) {
582
+ this.warnShutdownDrop("signal");
583
+ return;
584
+ }
435
585
  try {
436
- await postJson(url, body, this.authHeaders(), {
437
- maxAttempts: 3,
438
- debug: this.debug,
439
- sdkName: this.sdkName
440
- });
586
+ await postJson(url, body, this.requestHeaders(), opts);
441
587
  } catch (err) {
442
588
  const msg = err instanceof Error ? err.message : String(err);
443
- console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`);
589
+ rateLimitedLog(
590
+ `${this.prefix}.send_signal_failed`,
591
+ () => console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`)
592
+ );
444
593
  }
445
594
  }
446
595
  async identify(users) {
@@ -464,17 +613,27 @@ var EventShipper = class {
464
613
  if (!this.writeKey) return;
465
614
  if (body.length === 0) return;
466
615
  const url = `${this.baseUrl}users/identify`;
616
+ const opts = this.requestOpts();
617
+ if (!opts) {
618
+ this.warnShutdownDrop("identify");
619
+ return;
620
+ }
467
621
  try {
468
- await postJson(url, body, this.authHeaders(), {
469
- maxAttempts: 3,
470
- debug: this.debug,
471
- sdkName: this.sdkName
472
- });
622
+ await postJson(url, body, this.requestHeaders(), opts);
473
623
  } catch (err) {
474
624
  const msg = err instanceof Error ? err.message : String(err);
475
- console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`);
625
+ rateLimitedLog(
626
+ `${this.prefix}.send_identify_failed`,
627
+ () => console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`)
628
+ );
476
629
  }
477
630
  }
631
+ warnShutdownDrop(what) {
632
+ rateLimitedLog(
633
+ `${this.prefix}.shutdown_deadline`,
634
+ () => console.warn(`${this.prefix} shutdown flush deadline exceeded; dropping ${what}`)
635
+ );
636
+ }
478
637
  async flushOne(eventId) {
479
638
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
480
639
  if (!this.enabled) return;
@@ -550,11 +709,13 @@ var EventShipper = class {
550
709
  if (!isPending) this.sticky.delete(eventId);
551
710
  return;
552
711
  }
553
- const p = postJson(url, payload, this.authHeaders(), {
554
- maxAttempts: 3,
555
- debug: this.debug,
556
- sdkName: this.sdkName
557
- });
712
+ const opts = this.requestOpts();
713
+ if (!opts) {
714
+ this.warnShutdownDrop(`track_partial ${eventId}`);
715
+ if (!isPending) this.sticky.delete(eventId);
716
+ return;
717
+ }
718
+ const p = postJson(url, payload, this.requestHeaders(), opts);
558
719
  this.inFlight.add(p);
559
720
  try {
560
721
  try {
@@ -564,7 +725,10 @@ var EventShipper = class {
564
725
  }
565
726
  } catch (err) {
566
727
  const msg = err instanceof Error ? err.message : String(err);
567
- console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`);
728
+ rateLimitedLog(
729
+ `${this.prefix}.send_track_partial_failed`,
730
+ () => console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`)
731
+ );
568
732
  }
569
733
  } finally {
570
734
  this.inFlight.delete(p);
@@ -663,10 +827,24 @@ function redactJsonAttributeValue(key, value) {
663
827
  if (scrubbedJson === json) return void 0;
664
828
  return { stringValue: scrubbedJson };
665
829
  }
830
+ function applyOtelSpanAttributeLimit(limit) {
831
+ var _a, _b;
832
+ try {
833
+ 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;
834
+ if (!raw) return limit;
835
+ const parsed = Number.parseInt(raw, 10);
836
+ if (Number.isFinite(parsed) && parsed > 0) {
837
+ return Math.min(limit, parsed);
838
+ }
839
+ } catch (e) {
840
+ }
841
+ return limit;
842
+ }
666
843
  var TraceShipper = class {
667
844
  constructor(opts) {
668
845
  this.queue = [];
669
846
  this.inFlight = /* @__PURE__ */ new Set();
847
+ this.hasShutdown = false;
670
848
  var _a, _b, _c, _d, _e, _f, _g, _h, _i;
671
849
  this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
672
850
  this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
@@ -684,8 +862,45 @@ var TraceShipper = class {
684
862
  if (this.debug && this.localDebuggerUrl) {
685
863
  console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
686
864
  }
865
+ this.projectId = normalizeProjectId(opts.projectId, {
866
+ debug: this.debug,
867
+ prefix: this.prefix
868
+ });
687
869
  this.transformSpanHook = opts.transformSpan;
688
870
  this.disableDefaultRedaction = opts.disableDefaultRedaction === true;
871
+ this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
872
+ }
873
+ /**
874
+ * Cap every string attribute value on the span. O(#attributes) length
875
+ * checks; only oversized values pay a slice. Runs AFTER the redaction
876
+ * pipeline so the default secret-scrub still sees parseable JSON in
877
+ * `ai.request.providerOptions` / `ai.response.providerMetadata` (capping
878
+ * first could cut a JSON blob mid-way, fail the parse, and ship secrets
879
+ * in the surviving prefix).
880
+ *
881
+ * A stricter `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var is honored
882
+ * for span content, matching the Python SDK and the OTel SDK convention.
883
+ */
884
+ capSpanAttributes(span) {
885
+ var _a;
886
+ const maxChars = applyOtelSpanAttributeLimit(
887
+ resolveMaxTextFieldChars(this.maxTextFieldCharsOpt)
888
+ );
889
+ const attrs = span.attributes;
890
+ if (!attrs || attrs.length === 0) return span;
891
+ let nextAttrs;
892
+ for (let i = 0; i < attrs.length; i++) {
893
+ const attr = attrs[i];
894
+ const value = (_a = attr.value) == null ? void 0 : _a.stringValue;
895
+ if (typeof value !== "string" || value.length <= maxChars) continue;
896
+ if (!nextAttrs) nextAttrs = attrs.slice();
897
+ nextAttrs[i] = {
898
+ key: attr.key,
899
+ value: { ...attr.value, stringValue: capText(value, maxChars) }
900
+ };
901
+ }
902
+ if (!nextAttrs) return span;
903
+ return { ...span, attributes: nextAttrs };
689
904
  }
690
905
  /**
691
906
  * Apply the user `transformSpan` hook (if any) followed by the default
@@ -719,7 +934,7 @@ var TraceShipper = class {
719
934
  if (!this.disableDefaultRedaction) {
720
935
  current = defaultTransformSpan(current);
721
936
  }
722
- return current;
937
+ return this.capSpanAttributes(current);
723
938
  }
724
939
  isDebugEnabled() {
725
940
  return this.debug;
@@ -727,6 +942,9 @@ var TraceShipper = class {
727
942
  authHeaders() {
728
943
  return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
729
944
  }
945
+ requestHeaders() {
946
+ return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
947
+ }
730
948
  startSpan(args) {
731
949
  var _a, _b;
732
950
  const ids = createSpanIds(args.parent);
@@ -840,6 +1058,16 @@ var TraceShipper = class {
840
1058
  while (this.queue.length > 0) {
841
1059
  const batch = this.queue.splice(0, this.maxBatchSize);
842
1060
  if (!this.writeKey) continue;
1061
+ const opts = this.requestOpts();
1062
+ if (!opts) {
1063
+ rateLimitedLog(
1064
+ `${this.prefix}.shutdown_deadline`,
1065
+ () => console.warn(
1066
+ `${this.prefix} shutdown flush deadline exceeded; dropping ${batch.length} spans`
1067
+ )
1068
+ );
1069
+ continue;
1070
+ }
843
1071
  const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
844
1072
  const url = `${this.baseUrl}traces`;
845
1073
  if (this.debug) {
@@ -848,11 +1076,7 @@ var TraceShipper = class {
848
1076
  endpoint: url
849
1077
  });
850
1078
  }
851
- const p = postJson(url, body, this.authHeaders(), {
852
- maxAttempts: 3,
853
- debug: this.debug,
854
- sdkName: this.sdkName
855
- });
1079
+ const p = postJson(url, body, this.requestHeaders(), opts);
856
1080
  this.inFlight.add(p);
857
1081
  try {
858
1082
  try {
@@ -860,21 +1084,61 @@ var TraceShipper = class {
860
1084
  if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
861
1085
  } catch (err) {
862
1086
  const msg = err instanceof Error ? err.message : String(err);
863
- console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`);
1087
+ rateLimitedLog(
1088
+ `${this.prefix}.send_spans_failed`,
1089
+ () => console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`)
1090
+ );
864
1091
  }
865
1092
  } finally {
866
1093
  this.inFlight.delete(p);
867
1094
  }
868
1095
  }
869
1096
  }
1097
+ /** See EventShipper.requestOpts — same shutdown-budget semantics. */
1098
+ requestOpts() {
1099
+ if (this.shutdownDeadlineAt !== void 0) {
1100
+ const remainingMs = this.shutdownDeadlineAt - Date.now();
1101
+ if (remainingMs <= 0) return null;
1102
+ return {
1103
+ maxAttempts: 1,
1104
+ debug: this.debug,
1105
+ sdkName: this.sdkName,
1106
+ timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
1107
+ };
1108
+ }
1109
+ if (this.hasShutdown) {
1110
+ return {
1111
+ maxAttempts: 1,
1112
+ debug: this.debug,
1113
+ sdkName: this.sdkName,
1114
+ timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
1115
+ };
1116
+ }
1117
+ return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
1118
+ }
870
1119
  async shutdown() {
871
- if (this.timer) {
872
- clearTimeout(this.timer);
873
- this.timer = void 0;
1120
+ this.hasShutdown = true;
1121
+ this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
1122
+ try {
1123
+ if (this.timer) {
1124
+ clearTimeout(this.timer);
1125
+ this.timer = void 0;
1126
+ }
1127
+ const drain = async () => {
1128
+ await this.flush();
1129
+ await Promise.all([...this.inFlight].map((p) => p.catch(() => {
1130
+ })));
1131
+ };
1132
+ const settled = await raceWithTimeout(drain(), SHUTDOWN_DEADLINE_MS);
1133
+ if (!settled) {
1134
+ rateLimitedLog(
1135
+ `${this.prefix}.shutdown_deadline`,
1136
+ () => console.warn(`${this.prefix} shutdown flush deadline exceeded; abandoning in-flight spans`)
1137
+ );
1138
+ }
1139
+ } finally {
1140
+ this.shutdownDeadlineAt = void 0;
874
1141
  }
875
- await this.flush();
876
- await Promise.all([...this.inFlight].map((p) => p.catch(() => {
877
- })));
878
1142
  }
879
1143
  };
880
1144
 
@@ -885,7 +1149,7 @@ globalThis.RAINDROP_ASYNC_LOCAL_STORAGE = import_async_hooks.AsyncLocalStorage;
885
1149
  // package.json
886
1150
  var package_default = {
887
1151
  name: "@raindrop-ai/pi-agent",
888
- version: "0.0.4",
1152
+ version: "0.0.6",
889
1153
  description: "Raindrop observability for Pi Agent \u2014 automatic tracing via subscriber or pi-coding-agent extension",
890
1154
  type: "module",
891
1155
  license: "MIT",
@@ -1083,19 +1347,82 @@ function formatToolSpanName(toolName, args) {
1083
1347
  }
1084
1348
  return toolName;
1085
1349
  }
1086
- function safeStringify(value) {
1350
+ var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
1351
+ var MAX_TEXT_FIELD_CHARS = 1e6;
1352
+ var MAX_ATTR_LENGTH = 32768;
1353
+ var MAX_BOUNDED_DEPTH = 12;
1354
+ function capText2(value, limit = MAX_TEXT_FIELD_CHARS) {
1355
+ if (value.length <= limit) return value;
1356
+ if (limit > TRUNCATION_MARKER2.length) {
1357
+ return value.slice(0, limit - TRUNCATION_MARKER2.length) + TRUNCATION_MARKER2;
1358
+ }
1359
+ return value.slice(0, limit);
1360
+ }
1361
+ function boundedClone2(value, budget, depth) {
1362
+ if (budget.remaining <= 0) return TRUNCATION_MARKER2;
1363
+ if (typeof value === "string") {
1364
+ if (value.length > budget.remaining) {
1365
+ const taken = value.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
1366
+ budget.remaining = 0;
1367
+ return taken;
1368
+ }
1369
+ budget.remaining -= Math.max(value.length, 1);
1370
+ return value;
1371
+ }
1372
+ if (value === null || typeof value === "number" || typeof value === "boolean") {
1373
+ budget.remaining -= 8;
1374
+ return value;
1375
+ }
1376
+ if (typeof value !== "object") {
1377
+ budget.remaining -= 8;
1378
+ return value;
1379
+ }
1380
+ if (depth >= MAX_BOUNDED_DEPTH) {
1381
+ budget.remaining -= 16;
1382
+ return `<max depth: ${TRUNCATION_MARKER2}>`;
1383
+ }
1384
+ const withToJson = value;
1385
+ if (typeof withToJson.toJSON === "function") {
1386
+ try {
1387
+ return boundedClone2(withToJson.toJSON(), budget, depth + 1);
1388
+ } catch (e) {
1389
+ }
1390
+ }
1391
+ if (Array.isArray(value)) {
1392
+ const out2 = [];
1393
+ for (const item of value) {
1394
+ if (budget.remaining <= 0) {
1395
+ out2.push(TRUNCATION_MARKER2);
1396
+ break;
1397
+ }
1398
+ out2.push(boundedClone2(item, budget, depth + 1));
1399
+ }
1400
+ return out2;
1401
+ }
1402
+ const out = {};
1403
+ for (const key of Object.keys(value)) {
1404
+ if (budget.remaining <= 0) {
1405
+ out["..."] = TRUNCATION_MARKER2;
1406
+ break;
1407
+ }
1408
+ budget.remaining -= Math.max(key.length, 1);
1409
+ out[key] = boundedClone2(value[key], budget, depth + 1);
1410
+ }
1411
+ return out;
1412
+ }
1413
+ function safeStringify(value, limit = MAX_ATTR_LENGTH) {
1087
1414
  if (value === void 0 || value === null) return void 0;
1088
1415
  try {
1089
- return JSON.stringify(value);
1416
+ const pruned = boundedClone2(value, { remaining: limit + TRUNCATION_MARKER2.length + 256 }, 0);
1417
+ return JSON.stringify(pruned);
1090
1418
  } catch (e) {
1091
1419
  try {
1092
- return String(value);
1420
+ return capText2(String(value), limit);
1093
1421
  } catch (e2) {
1094
1422
  return "[unserializable]";
1095
1423
  }
1096
1424
  }
1097
1425
  }
1098
- var MAX_ATTR_LENGTH = 32768;
1099
1426
  function truncate(value) {
1100
1427
  if (value === void 0) return void 0;
1101
1428
  if (value.length <= MAX_ATTR_LENGTH) return value;
@@ -1212,9 +1539,10 @@ function createSubscriber(agent, eventShipper, traceShipper, defaultOptions, opt
1212
1539
  if (!currentRun) return;
1213
1540
  const userText = extractUserText(message);
1214
1541
  if (userText !== void 0) {
1215
- currentRun.currentInput = userText;
1542
+ const cappedInput = capText2(userText);
1543
+ currentRun.currentInput = cappedInput;
1216
1544
  if (eventShipper) {
1217
- eventShipper.patch(currentRun.eventId, { input: userText }).catch(() => {
1545
+ eventShipper.patch(currentRun.eventId, { input: cappedInput }).catch(() => {
1218
1546
  });
1219
1547
  }
1220
1548
  return;
@@ -1383,7 +1711,7 @@ function createSubscriber(agent, eventShipper, traceShipper, defaultOptions, opt
1383
1711
  traceShipper.endSpan(run.currentTurnSpan);
1384
1712
  run.currentTurnSpan = void 0;
1385
1713
  }
1386
- const outputText = run.outputParts.join("");
1714
+ const outputText = capText2(run.outputParts.join(""));
1387
1715
  if (traceShipper && run.rootSpan) {
1388
1716
  const rootAttrs = [
1389
1717
  attrString("ai.operationId", "generateText")
@@ -1501,6 +1829,7 @@ function createRaindropPiAgent(opts) {
1501
1829
  enabled: true,
1502
1830
  debug: ((_e = opts.events) == null ? void 0 : _e.debug) === true || envDebug,
1503
1831
  partialFlushMs: (_f = opts.events) == null ? void 0 : _f.partialFlushMs,
1832
+ projectId: opts.projectId,
1504
1833
  localDebuggerUrl: opts.localWorkshopUrl
1505
1834
  }) : null;
1506
1835
  const traceShipper = tracesEnabled && (hasWriteKey || opts.endpoint || hasLocalDestination) ? new TraceShipper2({
@@ -1512,6 +1841,7 @@ function createRaindropPiAgent(opts) {
1512
1841
  flushIntervalMs: (_i = opts.traces) == null ? void 0 : _i.flushIntervalMs,
1513
1842
  maxBatchSize: (_j = opts.traces) == null ? void 0 : _j.maxBatchSize,
1514
1843
  maxQueueSize: (_k = opts.traces) == null ? void 0 : _k.maxQueueSize,
1844
+ projectId: opts.projectId,
1515
1845
  localDebuggerUrl: opts.localWorkshopUrl
1516
1846
  }) : null;
1517
1847
  const defaultOptions = {