@raindrop-ai/pi-agent 0.0.4 → 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/extension.js CHANGED
@@ -3,6 +3,7 @@ import {
3
3
  TraceShipper,
4
4
  attrInt,
5
5
  attrString,
6
+ capText,
6
7
  extractAssistantText,
7
8
  formatToolSpanName,
8
9
  generateId,
@@ -13,7 +14,7 @@ import {
13
14
  resolveLocalDebuggerBaseUrl,
14
15
  safeStringify,
15
16
  truncate
16
- } from "./chunk-EWIO36KH.js";
17
+ } from "./chunk-2NGOEVWI.js";
17
18
 
18
19
  // src/internal/config.ts
19
20
  import { existsSync, readFileSync } from "fs";
@@ -78,6 +79,15 @@ function safeParsArgs(argsStr) {
78
79
  }
79
80
  }
80
81
  var MAX_SYSTEM_PROMPT_LENGTH = 32768;
82
+ var ERROR_LOG_INTERVAL_MS = 3e4;
83
+ var lastErrorLogAt = /* @__PURE__ */ new Map();
84
+ function rateLimitedErrorLog(key, message) {
85
+ const now = Date.now();
86
+ const last = lastErrorLogAt.get(key);
87
+ if (last !== void 0 && now - last < ERROR_LOG_INTERVAL_MS) return;
88
+ lastErrorLogAt.set(key, now);
89
+ console.log(message);
90
+ }
81
91
  function createSessionState(sessionId) {
82
92
  return {
83
93
  sessionId,
@@ -142,7 +152,8 @@ function getState(stateRef, ctx) {
142
152
  function registerTracing(pi, config, eventShipper, traceShipper) {
143
153
  const stateRef = {};
144
154
  function logError(hook, err) {
145
- console.log(
155
+ rateLimitedErrorLog(
156
+ hook,
146
157
  `[raindrop-ai/pi-agent] [error] Error in ${hook}: ${err instanceof Error ? err.message : String(err)}`
147
158
  );
148
159
  }
@@ -195,7 +206,7 @@ function registerTracing(pi, config, eventShipper, traceShipper) {
195
206
  }
196
207
  state.toolSpanStarts.clear();
197
208
  state.toolCallToLlmParent.clear();
198
- state.currentInput = event.prompt;
209
+ state.currentInput = capText(event.prompt);
199
210
  state.turnNumber = 0;
200
211
  state.currentSystemPrompt = config.captureSystemPrompt ? truncateSystemPrompt(event.systemPrompt) : void 0;
201
212
  state.currentEventRequestId = generateId();
@@ -216,7 +227,7 @@ function registerTracing(pi, config, eventShipper, traceShipper) {
216
227
  userId: getUserId(state, config.eventMetadata),
217
228
  convoId: state.sessionId,
218
229
  eventName: getEventName(config),
219
- input: event.prompt,
230
+ input: state.currentInput,
220
231
  ...attachments.length > 0 ? { attachments } : {},
221
232
  properties: getBaseProperties(config, ctx)
222
233
  });
@@ -267,7 +278,7 @@ function registerTracing(pi, config, eventShipper, traceShipper) {
267
278
  const modelId = (_c = message.model) != null ? _c : "";
268
279
  const modelName = provider && modelId ? `${provider}/${modelId}` : modelId || "llm";
269
280
  const errorForSpan = getAssistantError(message);
270
- const outputText = getAssistantText(message);
281
+ const outputText = capText(getAssistantText(message));
271
282
  const inputTokens = (_d = message.usage) == null ? void 0 : _d.input;
272
283
  const outputTokens = (_e = message.usage) == null ? void 0 : _e.output;
273
284
  const cacheReadTokens = (_f = message.usage) == null ? void 0 : _f.cacheRead;
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-U5HUTMR5.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,8 @@ function mirrorPartialEventToLocalDebugger(event, options = {}) {
310
376
  }).catch(() => {
311
377
  });
312
378
  }
379
+ var SHUTDOWN_DEADLINE_MS = 1e4;
380
+ var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
313
381
  function mergePatches(target, source) {
314
382
  var _a, _b, _c, _d;
315
383
  const out = { ...target, ...source };
@@ -327,6 +395,7 @@ var EventShipper = class {
327
395
  this.sticky = /* @__PURE__ */ new Map();
328
396
  this.timers = /* @__PURE__ */ new Map();
329
397
  this.inFlight = /* @__PURE__ */ new Set();
398
+ this.hasShutdown = false;
330
399
  var _a, _b, _c, _d, _e, _f, _g, _h;
331
400
  this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
332
401
  this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
@@ -336,6 +405,7 @@ var EventShipper = class {
336
405
  this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
337
406
  this.prefix = `[raindrop-ai/${this.sdkName}]`;
338
407
  this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
408
+ this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
339
409
  this.localDebuggerUrl = (_f = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _f : void 0;
340
410
  if (this.debug && this.localDebuggerUrl) {
341
411
  console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
@@ -358,10 +428,52 @@ var EventShipper = class {
358
428
  authHeaders() {
359
429
  return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
360
430
  }
431
+ /**
432
+ * Build the retry/timeout options for one POST, honoring the shutdown
433
+ * deadline. Returns `null` when the shutdown drain window is exhausted —
434
+ * the caller must drop the payload (with a rate-limited warning) instead
435
+ * of issuing a request that could outlive process exit.
436
+ *
437
+ * Checked fresh on EVERY send, so a shutdown that begins while the flush
438
+ * path is mid-drain takes effect immediately: no further retries, and the
439
+ * per-attempt timeout is clamped to the remaining window. After
440
+ * `shutdown()` returns (deadline cleared, `hasShutdown` still set),
441
+ * sends — late callers, or flush work the deadline abandoned mid-drain —
442
+ * run as a single short attempt rather than regaining the full retry
443
+ * schedule.
444
+ */
445
+ requestOpts() {
446
+ if (this.shutdownDeadlineAt !== void 0) {
447
+ const remainingMs = this.shutdownDeadlineAt - Date.now();
448
+ if (remainingMs <= 0) return null;
449
+ return {
450
+ maxAttempts: 1,
451
+ debug: this.debug,
452
+ sdkName: this.sdkName,
453
+ timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
454
+ };
455
+ }
456
+ if (this.hasShutdown) {
457
+ return {
458
+ maxAttempts: 1,
459
+ debug: this.debug,
460
+ sdkName: this.sdkName,
461
+ timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
462
+ };
463
+ }
464
+ return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
465
+ }
361
466
  async patch(eventId, patch) {
362
467
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
363
468
  if (!this.enabled) return;
364
469
  if (!eventId || !eventId.trim()) return;
470
+ const maxChars = resolveMaxTextFieldChars(this.maxTextFieldCharsOpt);
471
+ if (typeof patch.input === "string" && patch.input.length > maxChars) {
472
+ patch = { ...patch, input: capText(patch.input, maxChars) };
473
+ }
474
+ if (typeof patch.output === "string" && patch.output.length > maxChars) {
475
+ patch = { ...patch, output: capText(patch.output, maxChars) };
476
+ }
365
477
  if (this.debug) {
366
478
  console.log(`${this.prefix} queue patch`, {
367
479
  eventId,
@@ -408,9 +520,16 @@ var EventShipper = class {
408
520
  })));
409
521
  }
410
522
  async shutdown() {
411
- for (const t of this.timers.values()) clearTimeout(t);
412
- this.timers.clear();
413
- await this.flush();
523
+ this.hasShutdown = true;
524
+ this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
525
+ try {
526
+ for (const t of this.timers.values()) clearTimeout(t);
527
+ this.timers.clear();
528
+ const settled = await raceWithTimeout(this.flush(), SHUTDOWN_DEADLINE_MS);
529
+ if (!settled) this.warnShutdownDrop("in-flight request(s) at shutdown");
530
+ } finally {
531
+ this.shutdownDeadlineAt = void 0;
532
+ }
414
533
  }
415
534
  async trackSignal(signal) {
416
535
  var _a, _b;
@@ -432,15 +551,19 @@ var EventShipper = class {
432
551
  ];
433
552
  if (!this.writeKey) return;
434
553
  const url = `${this.baseUrl}signals/track`;
554
+ const opts = this.requestOpts();
555
+ if (!opts) {
556
+ this.warnShutdownDrop("signal");
557
+ return;
558
+ }
435
559
  try {
436
- await postJson(url, body, this.authHeaders(), {
437
- maxAttempts: 3,
438
- debug: this.debug,
439
- sdkName: this.sdkName
440
- });
560
+ await postJson(url, body, this.authHeaders(), opts);
441
561
  } catch (err) {
442
562
  const msg = err instanceof Error ? err.message : String(err);
443
- console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`);
563
+ rateLimitedLog(
564
+ `${this.prefix}.send_signal_failed`,
565
+ () => console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`)
566
+ );
444
567
  }
445
568
  }
446
569
  async identify(users) {
@@ -464,17 +587,27 @@ var EventShipper = class {
464
587
  if (!this.writeKey) return;
465
588
  if (body.length === 0) return;
466
589
  const url = `${this.baseUrl}users/identify`;
590
+ const opts = this.requestOpts();
591
+ if (!opts) {
592
+ this.warnShutdownDrop("identify");
593
+ return;
594
+ }
467
595
  try {
468
- await postJson(url, body, this.authHeaders(), {
469
- maxAttempts: 3,
470
- debug: this.debug,
471
- sdkName: this.sdkName
472
- });
596
+ await postJson(url, body, this.authHeaders(), opts);
473
597
  } catch (err) {
474
598
  const msg = err instanceof Error ? err.message : String(err);
475
- console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`);
599
+ rateLimitedLog(
600
+ `${this.prefix}.send_identify_failed`,
601
+ () => console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`)
602
+ );
476
603
  }
477
604
  }
605
+ warnShutdownDrop(what) {
606
+ rateLimitedLog(
607
+ `${this.prefix}.shutdown_deadline`,
608
+ () => console.warn(`${this.prefix} shutdown flush deadline exceeded; dropping ${what}`)
609
+ );
610
+ }
478
611
  async flushOne(eventId) {
479
612
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
480
613
  if (!this.enabled) return;
@@ -550,11 +683,13 @@ var EventShipper = class {
550
683
  if (!isPending) this.sticky.delete(eventId);
551
684
  return;
552
685
  }
553
- const p = postJson(url, payload, this.authHeaders(), {
554
- maxAttempts: 3,
555
- debug: this.debug,
556
- sdkName: this.sdkName
557
- });
686
+ const opts = this.requestOpts();
687
+ if (!opts) {
688
+ this.warnShutdownDrop(`track_partial ${eventId}`);
689
+ if (!isPending) this.sticky.delete(eventId);
690
+ return;
691
+ }
692
+ const p = postJson(url, payload, this.authHeaders(), opts);
558
693
  this.inFlight.add(p);
559
694
  try {
560
695
  try {
@@ -564,7 +699,10 @@ var EventShipper = class {
564
699
  }
565
700
  } catch (err) {
566
701
  const msg = err instanceof Error ? err.message : String(err);
567
- console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`);
702
+ rateLimitedLog(
703
+ `${this.prefix}.send_track_partial_failed`,
704
+ () => console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`)
705
+ );
568
706
  }
569
707
  } finally {
570
708
  this.inFlight.delete(p);
@@ -663,10 +801,24 @@ function redactJsonAttributeValue(key, value) {
663
801
  if (scrubbedJson === json) return void 0;
664
802
  return { stringValue: scrubbedJson };
665
803
  }
804
+ function applyOtelSpanAttributeLimit(limit) {
805
+ var _a, _b;
806
+ try {
807
+ 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;
808
+ if (!raw) return limit;
809
+ const parsed = Number.parseInt(raw, 10);
810
+ if (Number.isFinite(parsed) && parsed > 0) {
811
+ return Math.min(limit, parsed);
812
+ }
813
+ } catch (e) {
814
+ }
815
+ return limit;
816
+ }
666
817
  var TraceShipper = class {
667
818
  constructor(opts) {
668
819
  this.queue = [];
669
820
  this.inFlight = /* @__PURE__ */ new Set();
821
+ this.hasShutdown = false;
670
822
  var _a, _b, _c, _d, _e, _f, _g, _h, _i;
671
823
  this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
672
824
  this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
@@ -686,6 +838,39 @@ var TraceShipper = class {
686
838
  }
687
839
  this.transformSpanHook = opts.transformSpan;
688
840
  this.disableDefaultRedaction = opts.disableDefaultRedaction === true;
841
+ this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
842
+ }
843
+ /**
844
+ * Cap every string attribute value on the span. O(#attributes) length
845
+ * checks; only oversized values pay a slice. Runs AFTER the redaction
846
+ * pipeline so the default secret-scrub still sees parseable JSON in
847
+ * `ai.request.providerOptions` / `ai.response.providerMetadata` (capping
848
+ * first could cut a JSON blob mid-way, fail the parse, and ship secrets
849
+ * in the surviving prefix).
850
+ *
851
+ * A stricter `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var is honored
852
+ * for span content, matching the Python SDK and the OTel SDK convention.
853
+ */
854
+ capSpanAttributes(span) {
855
+ var _a;
856
+ const maxChars = applyOtelSpanAttributeLimit(
857
+ resolveMaxTextFieldChars(this.maxTextFieldCharsOpt)
858
+ );
859
+ const attrs = span.attributes;
860
+ if (!attrs || attrs.length === 0) return span;
861
+ let nextAttrs;
862
+ for (let i = 0; i < attrs.length; i++) {
863
+ const attr = attrs[i];
864
+ const value = (_a = attr.value) == null ? void 0 : _a.stringValue;
865
+ if (typeof value !== "string" || value.length <= maxChars) continue;
866
+ if (!nextAttrs) nextAttrs = attrs.slice();
867
+ nextAttrs[i] = {
868
+ key: attr.key,
869
+ value: { ...attr.value, stringValue: capText(value, maxChars) }
870
+ };
871
+ }
872
+ if (!nextAttrs) return span;
873
+ return { ...span, attributes: nextAttrs };
689
874
  }
690
875
  /**
691
876
  * Apply the user `transformSpan` hook (if any) followed by the default
@@ -719,7 +904,7 @@ var TraceShipper = class {
719
904
  if (!this.disableDefaultRedaction) {
720
905
  current = defaultTransformSpan(current);
721
906
  }
722
- return current;
907
+ return this.capSpanAttributes(current);
723
908
  }
724
909
  isDebugEnabled() {
725
910
  return this.debug;
@@ -840,6 +1025,16 @@ var TraceShipper = class {
840
1025
  while (this.queue.length > 0) {
841
1026
  const batch = this.queue.splice(0, this.maxBatchSize);
842
1027
  if (!this.writeKey) continue;
1028
+ const opts = this.requestOpts();
1029
+ if (!opts) {
1030
+ rateLimitedLog(
1031
+ `${this.prefix}.shutdown_deadline`,
1032
+ () => console.warn(
1033
+ `${this.prefix} shutdown flush deadline exceeded; dropping ${batch.length} spans`
1034
+ )
1035
+ );
1036
+ continue;
1037
+ }
843
1038
  const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
844
1039
  const url = `${this.baseUrl}traces`;
845
1040
  if (this.debug) {
@@ -848,11 +1043,7 @@ var TraceShipper = class {
848
1043
  endpoint: url
849
1044
  });
850
1045
  }
851
- const p = postJson(url, body, this.authHeaders(), {
852
- maxAttempts: 3,
853
- debug: this.debug,
854
- sdkName: this.sdkName
855
- });
1046
+ const p = postJson(url, body, this.authHeaders(), opts);
856
1047
  this.inFlight.add(p);
857
1048
  try {
858
1049
  try {
@@ -860,21 +1051,61 @@ var TraceShipper = class {
860
1051
  if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
861
1052
  } catch (err) {
862
1053
  const msg = err instanceof Error ? err.message : String(err);
863
- console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`);
1054
+ rateLimitedLog(
1055
+ `${this.prefix}.send_spans_failed`,
1056
+ () => console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`)
1057
+ );
864
1058
  }
865
1059
  } finally {
866
1060
  this.inFlight.delete(p);
867
1061
  }
868
1062
  }
869
1063
  }
1064
+ /** See EventShipper.requestOpts — same shutdown-budget semantics. */
1065
+ requestOpts() {
1066
+ if (this.shutdownDeadlineAt !== void 0) {
1067
+ const remainingMs = this.shutdownDeadlineAt - Date.now();
1068
+ if (remainingMs <= 0) return null;
1069
+ return {
1070
+ maxAttempts: 1,
1071
+ debug: this.debug,
1072
+ sdkName: this.sdkName,
1073
+ timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
1074
+ };
1075
+ }
1076
+ if (this.hasShutdown) {
1077
+ return {
1078
+ maxAttempts: 1,
1079
+ debug: this.debug,
1080
+ sdkName: this.sdkName,
1081
+ timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
1082
+ };
1083
+ }
1084
+ return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
1085
+ }
870
1086
  async shutdown() {
871
- if (this.timer) {
872
- clearTimeout(this.timer);
873
- this.timer = void 0;
1087
+ this.hasShutdown = true;
1088
+ this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
1089
+ try {
1090
+ if (this.timer) {
1091
+ clearTimeout(this.timer);
1092
+ this.timer = void 0;
1093
+ }
1094
+ const drain = async () => {
1095
+ await this.flush();
1096
+ await Promise.all([...this.inFlight].map((p) => p.catch(() => {
1097
+ })));
1098
+ };
1099
+ const settled = await raceWithTimeout(drain(), SHUTDOWN_DEADLINE_MS);
1100
+ if (!settled) {
1101
+ rateLimitedLog(
1102
+ `${this.prefix}.shutdown_deadline`,
1103
+ () => console.warn(`${this.prefix} shutdown flush deadline exceeded; abandoning in-flight spans`)
1104
+ );
1105
+ }
1106
+ } finally {
1107
+ this.shutdownDeadlineAt = void 0;
874
1108
  }
875
- await this.flush();
876
- await Promise.all([...this.inFlight].map((p) => p.catch(() => {
877
- })));
878
1109
  }
879
1110
  };
880
1111
 
@@ -885,7 +1116,7 @@ globalThis.RAINDROP_ASYNC_LOCAL_STORAGE = import_async_hooks.AsyncLocalStorage;
885
1116
  // package.json
886
1117
  var package_default = {
887
1118
  name: "@raindrop-ai/pi-agent",
888
- version: "0.0.4",
1119
+ version: "0.0.5",
889
1120
  description: "Raindrop observability for Pi Agent \u2014 automatic tracing via subscriber or pi-coding-agent extension",
890
1121
  type: "module",
891
1122
  license: "MIT",
@@ -1083,19 +1314,82 @@ function formatToolSpanName(toolName, args) {
1083
1314
  }
1084
1315
  return toolName;
1085
1316
  }
1086
- function safeStringify(value) {
1317
+ var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
1318
+ var MAX_TEXT_FIELD_CHARS = 1e6;
1319
+ var MAX_ATTR_LENGTH = 32768;
1320
+ var MAX_BOUNDED_DEPTH = 12;
1321
+ function capText2(value, limit = MAX_TEXT_FIELD_CHARS) {
1322
+ if (value.length <= limit) return value;
1323
+ if (limit > TRUNCATION_MARKER2.length) {
1324
+ return value.slice(0, limit - TRUNCATION_MARKER2.length) + TRUNCATION_MARKER2;
1325
+ }
1326
+ return value.slice(0, limit);
1327
+ }
1328
+ function boundedClone2(value, budget, depth) {
1329
+ if (budget.remaining <= 0) return TRUNCATION_MARKER2;
1330
+ if (typeof value === "string") {
1331
+ if (value.length > budget.remaining) {
1332
+ const taken = value.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
1333
+ budget.remaining = 0;
1334
+ return taken;
1335
+ }
1336
+ budget.remaining -= Math.max(value.length, 1);
1337
+ return value;
1338
+ }
1339
+ if (value === null || typeof value === "number" || typeof value === "boolean") {
1340
+ budget.remaining -= 8;
1341
+ return value;
1342
+ }
1343
+ if (typeof value !== "object") {
1344
+ budget.remaining -= 8;
1345
+ return value;
1346
+ }
1347
+ if (depth >= MAX_BOUNDED_DEPTH) {
1348
+ budget.remaining -= 16;
1349
+ return `<max depth: ${TRUNCATION_MARKER2}>`;
1350
+ }
1351
+ const withToJson = value;
1352
+ if (typeof withToJson.toJSON === "function") {
1353
+ try {
1354
+ return boundedClone2(withToJson.toJSON(), budget, depth + 1);
1355
+ } catch (e) {
1356
+ }
1357
+ }
1358
+ if (Array.isArray(value)) {
1359
+ const out2 = [];
1360
+ for (const item of value) {
1361
+ if (budget.remaining <= 0) {
1362
+ out2.push(TRUNCATION_MARKER2);
1363
+ break;
1364
+ }
1365
+ out2.push(boundedClone2(item, budget, depth + 1));
1366
+ }
1367
+ return out2;
1368
+ }
1369
+ const out = {};
1370
+ for (const key of Object.keys(value)) {
1371
+ if (budget.remaining <= 0) {
1372
+ out["..."] = TRUNCATION_MARKER2;
1373
+ break;
1374
+ }
1375
+ budget.remaining -= Math.max(key.length, 1);
1376
+ out[key] = boundedClone2(value[key], budget, depth + 1);
1377
+ }
1378
+ return out;
1379
+ }
1380
+ function safeStringify(value, limit = MAX_ATTR_LENGTH) {
1087
1381
  if (value === void 0 || value === null) return void 0;
1088
1382
  try {
1089
- return JSON.stringify(value);
1383
+ const pruned = boundedClone2(value, { remaining: limit + TRUNCATION_MARKER2.length + 256 }, 0);
1384
+ return JSON.stringify(pruned);
1090
1385
  } catch (e) {
1091
1386
  try {
1092
- return String(value);
1387
+ return capText2(String(value), limit);
1093
1388
  } catch (e2) {
1094
1389
  return "[unserializable]";
1095
1390
  }
1096
1391
  }
1097
1392
  }
1098
- var MAX_ATTR_LENGTH = 32768;
1099
1393
  function truncate(value) {
1100
1394
  if (value === void 0) return void 0;
1101
1395
  if (value.length <= MAX_ATTR_LENGTH) return value;
@@ -1212,9 +1506,10 @@ function createSubscriber(agent, eventShipper, traceShipper, defaultOptions, opt
1212
1506
  if (!currentRun) return;
1213
1507
  const userText = extractUserText(message);
1214
1508
  if (userText !== void 0) {
1215
- currentRun.currentInput = userText;
1509
+ const cappedInput = capText2(userText);
1510
+ currentRun.currentInput = cappedInput;
1216
1511
  if (eventShipper) {
1217
- eventShipper.patch(currentRun.eventId, { input: userText }).catch(() => {
1512
+ eventShipper.patch(currentRun.eventId, { input: cappedInput }).catch(() => {
1218
1513
  });
1219
1514
  }
1220
1515
  return;
@@ -1383,7 +1678,7 @@ function createSubscriber(agent, eventShipper, traceShipper, defaultOptions, opt
1383
1678
  traceShipper.endSpan(run.currentTurnSpan);
1384
1679
  run.currentTurnSpan = void 0;
1385
1680
  }
1386
- const outputText = run.outputParts.join("");
1681
+ const outputText = capText2(run.outputParts.join(""));
1387
1682
  if (traceShipper && run.rootSpan) {
1388
1683
  const rootAttrs = [
1389
1684
  attrString("ai.operationId", "generateText")