@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.
@@ -25,7 +25,7 @@ __export(extension_exports, {
25
25
  });
26
26
  module.exports = __toCommonJS(extension_exports);
27
27
 
28
- // ../core/dist/chunk-VUNUOE2X.js
28
+ // ../core/dist/chunk-SK6EJEO7.js
29
29
  function getCrypto() {
30
30
  const c = globalThis.crypto;
31
31
  return c;
@@ -72,6 +72,8 @@ function base64Encode(bytes) {
72
72
  function generateId() {
73
73
  return base64Encode(randomBytes(12)).replace(/[+/=]/g, "").slice(0, 16);
74
74
  }
75
+ var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
76
+ var MAX_RETRY_DELAY_MS = 3e4;
75
77
  function wait(ms) {
76
78
  return new Promise((resolve) => setTimeout(resolve, ms));
77
79
  }
@@ -79,6 +81,46 @@ function formatEndpoint(endpoint) {
79
81
  if (!endpoint) return void 0;
80
82
  return endpoint.endsWith("/") ? endpoint : `${endpoint}/`;
81
83
  }
84
+ function redactUrlForLog(url) {
85
+ try {
86
+ const parsed = new URL(url);
87
+ parsed.username = "";
88
+ parsed.password = "";
89
+ parsed.search = "";
90
+ parsed.hash = "";
91
+ return parsed.toString();
92
+ } catch (e) {
93
+ return "<unparseable-url>";
94
+ }
95
+ }
96
+ var RATE_LIMITED_LOG_INTERVAL_MS = 3e4;
97
+ var rateLimitedLogLast = /* @__PURE__ */ new Map();
98
+ function rateLimitedLog(key, log) {
99
+ const now = Date.now();
100
+ const last = rateLimitedLogLast.get(key);
101
+ if (last !== void 0 && now - last < RATE_LIMITED_LOG_INTERVAL_MS) {
102
+ return false;
103
+ }
104
+ rateLimitedLogLast.set(key, now);
105
+ log();
106
+ return true;
107
+ }
108
+ async function raceWithTimeout(promise, timeoutMs) {
109
+ let timer;
110
+ const settledInTime = await Promise.race([
111
+ promise.then(
112
+ () => true,
113
+ () => true
114
+ ),
115
+ new Promise((resolve) => {
116
+ var _a;
117
+ timer = setTimeout(() => resolve(false), Math.max(0, timeoutMs));
118
+ (_a = timer.unref) == null ? void 0 : _a.call(timer);
119
+ })
120
+ ]);
121
+ if (timer) clearTimeout(timer);
122
+ return settledInTime;
123
+ }
82
124
  function parseRetryAfter(headers) {
83
125
  var _a;
84
126
  const value = (_a = headers.get("Retry-After")) != null ? _a : headers.get("retry-after");
@@ -95,12 +137,12 @@ function parseRetryAfter(headers) {
95
137
  function getRetryDelayMs(attemptNumber, previousError) {
96
138
  if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
97
139
  const v = previousError.retryAfterMs;
98
- if (typeof v === "number") return Math.max(0, v);
140
+ if (typeof v === "number") return Math.min(Math.max(0, v), MAX_RETRY_DELAY_MS);
99
141
  }
100
142
  if (attemptNumber <= 1) return 0;
101
143
  const base = 500;
102
144
  const factor = Math.pow(2, attemptNumber - 2);
103
- return base * factor;
145
+ return Math.min(base * factor, MAX_RETRY_DELAY_MS);
104
146
  }
105
147
  async function withRetry(operation, opName, opts) {
106
148
  const prefix = opts.sdkName ? `[raindrop-ai/${opts.sdkName}]` : "[raindrop-ai/core]";
@@ -135,7 +177,9 @@ async function withRetry(operation, opName, opts) {
135
177
  throw lastError instanceof Error ? lastError : new Error(String(lastError));
136
178
  }
137
179
  async function postJson(url, body, headers, opts) {
138
- const opName = `POST ${url}`;
180
+ var _a;
181
+ const opName = `POST ${redactUrlForLog(url)}`;
182
+ const timeoutMs = (_a = opts.timeoutMs) != null ? _a : DEFAULT_REQUEST_TIMEOUT_MS;
139
183
  await withRetry(
140
184
  async () => {
141
185
  const resp = await fetch(url, {
@@ -144,7 +188,8 @@ async function postJson(url, body, headers, opts) {
144
188
  "Content-Type": "application/json",
145
189
  ...headers
146
190
  },
147
- body: JSON.stringify(body)
191
+ body: JSON.stringify(body),
192
+ signal: AbortSignal.timeout(timeoutMs)
148
193
  });
149
194
  if (!resp.ok) {
150
195
  const text = await resp.text().catch(() => "");
@@ -161,6 +206,27 @@ async function postJson(url, body, headers, opts) {
161
206
  opts
162
207
  );
163
208
  }
209
+ var DEFAULT_MAX_TEXT_FIELD_CHARS = 1e6;
210
+ var TRUNCATION_MARKER = "...[truncated by raindrop]";
211
+ var currentDefaultMaxTextFieldChars = DEFAULT_MAX_TEXT_FIELD_CHARS;
212
+ function resolveMaxTextFieldChars(value) {
213
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
214
+ return Math.floor(value);
215
+ }
216
+ return currentDefaultMaxTextFieldChars;
217
+ }
218
+ function truncateToLimit(text, limit) {
219
+ if (limit > TRUNCATION_MARKER.length) {
220
+ return text.slice(0, limit - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;
221
+ }
222
+ return text.slice(0, Math.max(0, limit));
223
+ }
224
+ function capText(value, limit) {
225
+ if (typeof value !== "string") return value;
226
+ const max = limit != null ? limit : currentDefaultMaxTextFieldChars;
227
+ if (value.length <= max) return value;
228
+ return truncateToLimit(value, max);
229
+ }
164
230
  var SpanStatusCode = {
165
231
  UNSET: 0,
166
232
  OK: 1,
@@ -303,6 +369,27 @@ function mirrorPartialEventToLocalDebugger(event, options = {}) {
303
369
  }).catch(() => {
304
370
  });
305
371
  }
372
+ var PROJECT_ID_HEADER = "X-Raindrop-Project-Id";
373
+ var PROJECT_ID_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
374
+ function isValidProjectIdSlug(value) {
375
+ return PROJECT_ID_SLUG_PATTERN.test(value);
376
+ }
377
+ function normalizeProjectId(raw, opts) {
378
+ if (typeof raw !== "string") return void 0;
379
+ const trimmed = raw.trim();
380
+ if (!trimmed) return void 0;
381
+ if (!isValidProjectIdSlug(trimmed) && opts.debug) {
382
+ console.warn(
383
+ `${opts.prefix} projectId "${trimmed}" does not match slug ${PROJECT_ID_SLUG_PATTERN.source}; sending anyway \u2014 backend may reject with HTTP 400`
384
+ );
385
+ }
386
+ return trimmed;
387
+ }
388
+ function projectIdHeaders(projectId) {
389
+ return projectId ? { [PROJECT_ID_HEADER]: projectId } : {};
390
+ }
391
+ var SHUTDOWN_DEADLINE_MS = 1e4;
392
+ var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
306
393
  function mergePatches(target, source) {
307
394
  var _a, _b, _c, _d;
308
395
  const out = { ...target, ...source };
@@ -320,6 +407,7 @@ var EventShipper = class {
320
407
  this.sticky = /* @__PURE__ */ new Map();
321
408
  this.timers = /* @__PURE__ */ new Map();
322
409
  this.inFlight = /* @__PURE__ */ new Set();
410
+ this.hasShutdown = false;
323
411
  var _a, _b, _c, _d, _e, _f, _g, _h;
324
412
  this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
325
413
  this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
@@ -329,10 +417,15 @@ var EventShipper = class {
329
417
  this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
330
418
  this.prefix = `[raindrop-ai/${this.sdkName}]`;
331
419
  this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
420
+ this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
332
421
  this.localDebuggerUrl = (_f = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _f : void 0;
333
422
  if (this.debug && this.localDebuggerUrl) {
334
423
  console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
335
424
  }
425
+ this.projectId = normalizeProjectId(opts.projectId, {
426
+ debug: this.debug,
427
+ prefix: this.prefix
428
+ });
336
429
  const isNode = typeof process !== "undefined" && typeof process.version === "string";
337
430
  this.context = {
338
431
  library: {
@@ -351,10 +444,55 @@ var EventShipper = class {
351
444
  authHeaders() {
352
445
  return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
353
446
  }
447
+ requestHeaders() {
448
+ return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
449
+ }
450
+ /**
451
+ * Build the retry/timeout options for one POST, honoring the shutdown
452
+ * deadline. Returns `null` when the shutdown drain window is exhausted —
453
+ * the caller must drop the payload (with a rate-limited warning) instead
454
+ * of issuing a request that could outlive process exit.
455
+ *
456
+ * Checked fresh on EVERY send, so a shutdown that begins while the flush
457
+ * path is mid-drain takes effect immediately: no further retries, and the
458
+ * per-attempt timeout is clamped to the remaining window. After
459
+ * `shutdown()` returns (deadline cleared, `hasShutdown` still set),
460
+ * sends — late callers, or flush work the deadline abandoned mid-drain —
461
+ * run as a single short attempt rather than regaining the full retry
462
+ * schedule.
463
+ */
464
+ requestOpts() {
465
+ if (this.shutdownDeadlineAt !== void 0) {
466
+ const remainingMs = this.shutdownDeadlineAt - Date.now();
467
+ if (remainingMs <= 0) return null;
468
+ return {
469
+ maxAttempts: 1,
470
+ debug: this.debug,
471
+ sdkName: this.sdkName,
472
+ timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
473
+ };
474
+ }
475
+ if (this.hasShutdown) {
476
+ return {
477
+ maxAttempts: 1,
478
+ debug: this.debug,
479
+ sdkName: this.sdkName,
480
+ timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
481
+ };
482
+ }
483
+ return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
484
+ }
354
485
  async patch(eventId, patch) {
355
486
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
356
487
  if (!this.enabled) return;
357
488
  if (!eventId || !eventId.trim()) return;
489
+ const maxChars = resolveMaxTextFieldChars(this.maxTextFieldCharsOpt);
490
+ if (typeof patch.input === "string" && patch.input.length > maxChars) {
491
+ patch = { ...patch, input: capText(patch.input, maxChars) };
492
+ }
493
+ if (typeof patch.output === "string" && patch.output.length > maxChars) {
494
+ patch = { ...patch, output: capText(patch.output, maxChars) };
495
+ }
358
496
  if (this.debug) {
359
497
  console.log(`${this.prefix} queue patch`, {
360
498
  eventId,
@@ -401,9 +539,16 @@ var EventShipper = class {
401
539
  })));
402
540
  }
403
541
  async shutdown() {
404
- for (const t of this.timers.values()) clearTimeout(t);
405
- this.timers.clear();
406
- await this.flush();
542
+ this.hasShutdown = true;
543
+ this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
544
+ try {
545
+ for (const t of this.timers.values()) clearTimeout(t);
546
+ this.timers.clear();
547
+ const settled = await raceWithTimeout(this.flush(), SHUTDOWN_DEADLINE_MS);
548
+ if (!settled) this.warnShutdownDrop("in-flight request(s) at shutdown");
549
+ } finally {
550
+ this.shutdownDeadlineAt = void 0;
551
+ }
407
552
  }
408
553
  async trackSignal(signal) {
409
554
  var _a, _b;
@@ -425,15 +570,19 @@ var EventShipper = class {
425
570
  ];
426
571
  if (!this.writeKey) return;
427
572
  const url = `${this.baseUrl}signals/track`;
573
+ const opts = this.requestOpts();
574
+ if (!opts) {
575
+ this.warnShutdownDrop("signal");
576
+ return;
577
+ }
428
578
  try {
429
- await postJson(url, body, this.authHeaders(), {
430
- maxAttempts: 3,
431
- debug: this.debug,
432
- sdkName: this.sdkName
433
- });
579
+ await postJson(url, body, this.requestHeaders(), opts);
434
580
  } catch (err) {
435
581
  const msg = err instanceof Error ? err.message : String(err);
436
- console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`);
582
+ rateLimitedLog(
583
+ `${this.prefix}.send_signal_failed`,
584
+ () => console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`)
585
+ );
437
586
  }
438
587
  }
439
588
  async identify(users) {
@@ -457,17 +606,27 @@ var EventShipper = class {
457
606
  if (!this.writeKey) return;
458
607
  if (body.length === 0) return;
459
608
  const url = `${this.baseUrl}users/identify`;
609
+ const opts = this.requestOpts();
610
+ if (!opts) {
611
+ this.warnShutdownDrop("identify");
612
+ return;
613
+ }
460
614
  try {
461
- await postJson(url, body, this.authHeaders(), {
462
- maxAttempts: 3,
463
- debug: this.debug,
464
- sdkName: this.sdkName
465
- });
615
+ await postJson(url, body, this.requestHeaders(), opts);
466
616
  } catch (err) {
467
617
  const msg = err instanceof Error ? err.message : String(err);
468
- console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`);
618
+ rateLimitedLog(
619
+ `${this.prefix}.send_identify_failed`,
620
+ () => console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`)
621
+ );
469
622
  }
470
623
  }
624
+ warnShutdownDrop(what) {
625
+ rateLimitedLog(
626
+ `${this.prefix}.shutdown_deadline`,
627
+ () => console.warn(`${this.prefix} shutdown flush deadline exceeded; dropping ${what}`)
628
+ );
629
+ }
471
630
  async flushOne(eventId) {
472
631
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
473
632
  if (!this.enabled) return;
@@ -543,11 +702,13 @@ var EventShipper = class {
543
702
  if (!isPending) this.sticky.delete(eventId);
544
703
  return;
545
704
  }
546
- const p = postJson(url, payload, this.authHeaders(), {
547
- maxAttempts: 3,
548
- debug: this.debug,
549
- sdkName: this.sdkName
550
- });
705
+ const opts = this.requestOpts();
706
+ if (!opts) {
707
+ this.warnShutdownDrop(`track_partial ${eventId}`);
708
+ if (!isPending) this.sticky.delete(eventId);
709
+ return;
710
+ }
711
+ const p = postJson(url, payload, this.requestHeaders(), opts);
551
712
  this.inFlight.add(p);
552
713
  try {
553
714
  try {
@@ -557,7 +718,10 @@ var EventShipper = class {
557
718
  }
558
719
  } catch (err) {
559
720
  const msg = err instanceof Error ? err.message : String(err);
560
- console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`);
721
+ rateLimitedLog(
722
+ `${this.prefix}.send_track_partial_failed`,
723
+ () => console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`)
724
+ );
561
725
  }
562
726
  } finally {
563
727
  this.inFlight.delete(p);
@@ -656,10 +820,24 @@ function redactJsonAttributeValue(key, value) {
656
820
  if (scrubbedJson === json) return void 0;
657
821
  return { stringValue: scrubbedJson };
658
822
  }
823
+ function applyOtelSpanAttributeLimit(limit) {
824
+ var _a, _b;
825
+ try {
826
+ 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;
827
+ if (!raw) return limit;
828
+ const parsed = Number.parseInt(raw, 10);
829
+ if (Number.isFinite(parsed) && parsed > 0) {
830
+ return Math.min(limit, parsed);
831
+ }
832
+ } catch (e) {
833
+ }
834
+ return limit;
835
+ }
659
836
  var TraceShipper = class {
660
837
  constructor(opts) {
661
838
  this.queue = [];
662
839
  this.inFlight = /* @__PURE__ */ new Set();
840
+ this.hasShutdown = false;
663
841
  var _a, _b, _c, _d, _e, _f, _g, _h, _i;
664
842
  this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
665
843
  this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
@@ -677,8 +855,45 @@ var TraceShipper = class {
677
855
  if (this.debug && this.localDebuggerUrl) {
678
856
  console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
679
857
  }
858
+ this.projectId = normalizeProjectId(opts.projectId, {
859
+ debug: this.debug,
860
+ prefix: this.prefix
861
+ });
680
862
  this.transformSpanHook = opts.transformSpan;
681
863
  this.disableDefaultRedaction = opts.disableDefaultRedaction === true;
864
+ this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
865
+ }
866
+ /**
867
+ * Cap every string attribute value on the span. O(#attributes) length
868
+ * checks; only oversized values pay a slice. Runs AFTER the redaction
869
+ * pipeline so the default secret-scrub still sees parseable JSON in
870
+ * `ai.request.providerOptions` / `ai.response.providerMetadata` (capping
871
+ * first could cut a JSON blob mid-way, fail the parse, and ship secrets
872
+ * in the surviving prefix).
873
+ *
874
+ * A stricter `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var is honored
875
+ * for span content, matching the Python SDK and the OTel SDK convention.
876
+ */
877
+ capSpanAttributes(span) {
878
+ var _a;
879
+ const maxChars = applyOtelSpanAttributeLimit(
880
+ resolveMaxTextFieldChars(this.maxTextFieldCharsOpt)
881
+ );
882
+ const attrs = span.attributes;
883
+ if (!attrs || attrs.length === 0) return span;
884
+ let nextAttrs;
885
+ for (let i = 0; i < attrs.length; i++) {
886
+ const attr = attrs[i];
887
+ const value = (_a = attr.value) == null ? void 0 : _a.stringValue;
888
+ if (typeof value !== "string" || value.length <= maxChars) continue;
889
+ if (!nextAttrs) nextAttrs = attrs.slice();
890
+ nextAttrs[i] = {
891
+ key: attr.key,
892
+ value: { ...attr.value, stringValue: capText(value, maxChars) }
893
+ };
894
+ }
895
+ if (!nextAttrs) return span;
896
+ return { ...span, attributes: nextAttrs };
682
897
  }
683
898
  /**
684
899
  * Apply the user `transformSpan` hook (if any) followed by the default
@@ -712,7 +927,7 @@ var TraceShipper = class {
712
927
  if (!this.disableDefaultRedaction) {
713
928
  current = defaultTransformSpan(current);
714
929
  }
715
- return current;
930
+ return this.capSpanAttributes(current);
716
931
  }
717
932
  isDebugEnabled() {
718
933
  return this.debug;
@@ -720,6 +935,9 @@ var TraceShipper = class {
720
935
  authHeaders() {
721
936
  return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
722
937
  }
938
+ requestHeaders() {
939
+ return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
940
+ }
723
941
  startSpan(args) {
724
942
  var _a, _b;
725
943
  const ids = createSpanIds(args.parent);
@@ -833,6 +1051,16 @@ var TraceShipper = class {
833
1051
  while (this.queue.length > 0) {
834
1052
  const batch = this.queue.splice(0, this.maxBatchSize);
835
1053
  if (!this.writeKey) continue;
1054
+ const opts = this.requestOpts();
1055
+ if (!opts) {
1056
+ rateLimitedLog(
1057
+ `${this.prefix}.shutdown_deadline`,
1058
+ () => console.warn(
1059
+ `${this.prefix} shutdown flush deadline exceeded; dropping ${batch.length} spans`
1060
+ )
1061
+ );
1062
+ continue;
1063
+ }
836
1064
  const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
837
1065
  const url = `${this.baseUrl}traces`;
838
1066
  if (this.debug) {
@@ -841,11 +1069,7 @@ var TraceShipper = class {
841
1069
  endpoint: url
842
1070
  });
843
1071
  }
844
- const p = postJson(url, body, this.authHeaders(), {
845
- maxAttempts: 3,
846
- debug: this.debug,
847
- sdkName: this.sdkName
848
- });
1072
+ const p = postJson(url, body, this.requestHeaders(), opts);
849
1073
  this.inFlight.add(p);
850
1074
  try {
851
1075
  try {
@@ -853,21 +1077,61 @@ var TraceShipper = class {
853
1077
  if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
854
1078
  } catch (err) {
855
1079
  const msg = err instanceof Error ? err.message : String(err);
856
- console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`);
1080
+ rateLimitedLog(
1081
+ `${this.prefix}.send_spans_failed`,
1082
+ () => console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`)
1083
+ );
857
1084
  }
858
1085
  } finally {
859
1086
  this.inFlight.delete(p);
860
1087
  }
861
1088
  }
862
1089
  }
1090
+ /** See EventShipper.requestOpts — same shutdown-budget semantics. */
1091
+ requestOpts() {
1092
+ if (this.shutdownDeadlineAt !== void 0) {
1093
+ const remainingMs = this.shutdownDeadlineAt - Date.now();
1094
+ if (remainingMs <= 0) return null;
1095
+ return {
1096
+ maxAttempts: 1,
1097
+ debug: this.debug,
1098
+ sdkName: this.sdkName,
1099
+ timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
1100
+ };
1101
+ }
1102
+ if (this.hasShutdown) {
1103
+ return {
1104
+ maxAttempts: 1,
1105
+ debug: this.debug,
1106
+ sdkName: this.sdkName,
1107
+ timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
1108
+ };
1109
+ }
1110
+ return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
1111
+ }
863
1112
  async shutdown() {
864
- if (this.timer) {
865
- clearTimeout(this.timer);
866
- this.timer = void 0;
1113
+ this.hasShutdown = true;
1114
+ this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
1115
+ try {
1116
+ if (this.timer) {
1117
+ clearTimeout(this.timer);
1118
+ this.timer = void 0;
1119
+ }
1120
+ const drain = async () => {
1121
+ await this.flush();
1122
+ await Promise.all([...this.inFlight].map((p) => p.catch(() => {
1123
+ })));
1124
+ };
1125
+ const settled = await raceWithTimeout(drain(), SHUTDOWN_DEADLINE_MS);
1126
+ if (!settled) {
1127
+ rateLimitedLog(
1128
+ `${this.prefix}.shutdown_deadline`,
1129
+ () => console.warn(`${this.prefix} shutdown flush deadline exceeded; abandoning in-flight spans`)
1130
+ );
1131
+ }
1132
+ } finally {
1133
+ this.shutdownDeadlineAt = void 0;
867
1134
  }
868
- await this.flush();
869
- await Promise.all([...this.inFlight].map((p) => p.catch(() => {
870
- })));
871
1135
  }
872
1136
  };
873
1137
 
@@ -884,7 +1148,7 @@ function getPiAgentDirectory() {
884
1148
  return (_a = process.env["PI_CODING_AGENT_DIR"]) != null ? _a : (0, import_node_path.join)((0, import_node_os.homedir)(), ".pi", "agent");
885
1149
  }
886
1150
  function loadConfig(projectDirectory) {
887
- var _a, _b, _c, _d, _e, _f, _g;
1151
+ var _a, _b, _c, _d, _e, _f, _g, _h;
888
1152
  let merged = {};
889
1153
  const configPaths = [
890
1154
  (0, import_node_path.join)(getPiAgentDirectory(), "raindrop.json"),
@@ -911,9 +1175,10 @@ function loadConfig(projectDirectory) {
911
1175
  return {
912
1176
  writeKey: (_b = (_a = process.env["RAINDROP_WRITE_KEY"]) != null ? _a : merged.write_key) != null ? _b : "",
913
1177
  endpoint: (_d = (_c = process.env["RAINDROP_API_URL"]) != null ? _c : merged.api_url) != null ? _d : "https://api.raindrop.ai/v1",
914
- eventName: (_e = merged.event_name) != null ? _e : "pi_session",
915
- debug: process.env["RAINDROP_DEBUG"] === "true" ? true : (_f = merged.debug) != null ? _f : false,
916
- captureSystemPrompt: process.env["RAINDROP_CAPTURE_SYSTEM_PROMPT"] !== void 0 ? process.env["RAINDROP_CAPTURE_SYSTEM_PROMPT"] === "true" : (_g = merged.capture_system_prompt) != null ? _g : false,
1178
+ projectId: (_e = process.env["RAINDROP_PROJECT_ID"]) != null ? _e : merged.project_id,
1179
+ eventName: (_f = merged.event_name) != null ? _f : "pi_session",
1180
+ debug: process.env["RAINDROP_DEBUG"] === "true" ? true : (_g = merged.debug) != null ? _g : false,
1181
+ captureSystemPrompt: process.env["RAINDROP_CAPTURE_SYSTEM_PROMPT"] !== void 0 ? process.env["RAINDROP_CAPTURE_SYSTEM_PROMPT"] === "true" : (_h = merged.capture_system_prompt) != null ? _h : false,
917
1182
  eventMetadata,
918
1183
  localWorkshopUrl: resolveLocalWorkshopUrl(merged.local_workshop_url)
919
1184
  };
@@ -932,7 +1197,7 @@ function resolveLocalWorkshopUrl(fileValue) {
932
1197
  // package.json
933
1198
  var package_default = {
934
1199
  name: "@raindrop-ai/pi-agent",
935
- version: "0.0.4",
1200
+ version: "0.0.6",
936
1201
  description: "Raindrop observability for Pi Agent \u2014 automatic tracing via subscriber or pi-coding-agent extension",
937
1202
  type: "module",
938
1203
  license: "MIT",
@@ -1087,19 +1352,82 @@ function formatToolSpanName(toolName, args) {
1087
1352
  }
1088
1353
  return toolName;
1089
1354
  }
1090
- function safeStringify(value) {
1355
+ var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
1356
+ var MAX_TEXT_FIELD_CHARS = 1e6;
1357
+ var MAX_ATTR_LENGTH = 32768;
1358
+ var MAX_BOUNDED_DEPTH = 12;
1359
+ function capText2(value, limit = MAX_TEXT_FIELD_CHARS) {
1360
+ if (value.length <= limit) return value;
1361
+ if (limit > TRUNCATION_MARKER2.length) {
1362
+ return value.slice(0, limit - TRUNCATION_MARKER2.length) + TRUNCATION_MARKER2;
1363
+ }
1364
+ return value.slice(0, limit);
1365
+ }
1366
+ function boundedClone2(value, budget, depth) {
1367
+ if (budget.remaining <= 0) return TRUNCATION_MARKER2;
1368
+ if (typeof value === "string") {
1369
+ if (value.length > budget.remaining) {
1370
+ const taken = value.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
1371
+ budget.remaining = 0;
1372
+ return taken;
1373
+ }
1374
+ budget.remaining -= Math.max(value.length, 1);
1375
+ return value;
1376
+ }
1377
+ if (value === null || typeof value === "number" || typeof value === "boolean") {
1378
+ budget.remaining -= 8;
1379
+ return value;
1380
+ }
1381
+ if (typeof value !== "object") {
1382
+ budget.remaining -= 8;
1383
+ return value;
1384
+ }
1385
+ if (depth >= MAX_BOUNDED_DEPTH) {
1386
+ budget.remaining -= 16;
1387
+ return `<max depth: ${TRUNCATION_MARKER2}>`;
1388
+ }
1389
+ const withToJson = value;
1390
+ if (typeof withToJson.toJSON === "function") {
1391
+ try {
1392
+ return boundedClone2(withToJson.toJSON(), budget, depth + 1);
1393
+ } catch (e) {
1394
+ }
1395
+ }
1396
+ if (Array.isArray(value)) {
1397
+ const out2 = [];
1398
+ for (const item of value) {
1399
+ if (budget.remaining <= 0) {
1400
+ out2.push(TRUNCATION_MARKER2);
1401
+ break;
1402
+ }
1403
+ out2.push(boundedClone2(item, budget, depth + 1));
1404
+ }
1405
+ return out2;
1406
+ }
1407
+ const out = {};
1408
+ for (const key of Object.keys(value)) {
1409
+ if (budget.remaining <= 0) {
1410
+ out["..."] = TRUNCATION_MARKER2;
1411
+ break;
1412
+ }
1413
+ budget.remaining -= Math.max(key.length, 1);
1414
+ out[key] = boundedClone2(value[key], budget, depth + 1);
1415
+ }
1416
+ return out;
1417
+ }
1418
+ function safeStringify(value, limit = MAX_ATTR_LENGTH) {
1091
1419
  if (value === void 0 || value === null) return void 0;
1092
1420
  try {
1093
- return JSON.stringify(value);
1421
+ const pruned = boundedClone2(value, { remaining: limit + TRUNCATION_MARKER2.length + 256 }, 0);
1422
+ return JSON.stringify(pruned);
1094
1423
  } catch (e) {
1095
1424
  try {
1096
- return String(value);
1425
+ return capText2(String(value), limit);
1097
1426
  } catch (e2) {
1098
1427
  return "[unserializable]";
1099
1428
  }
1100
1429
  }
1101
1430
  }
1102
- var MAX_ATTR_LENGTH = 32768;
1103
1431
  function truncate(value) {
1104
1432
  if (value === void 0) return void 0;
1105
1433
  if (value.length <= MAX_ATTR_LENGTH) return value;
@@ -1138,6 +1466,15 @@ function safeParsArgs(argsStr) {
1138
1466
  }
1139
1467
  }
1140
1468
  var MAX_SYSTEM_PROMPT_LENGTH = 32768;
1469
+ var ERROR_LOG_INTERVAL_MS = 3e4;
1470
+ var lastErrorLogAt = /* @__PURE__ */ new Map();
1471
+ function rateLimitedErrorLog(key, message) {
1472
+ const now = Date.now();
1473
+ const last = lastErrorLogAt.get(key);
1474
+ if (last !== void 0 && now - last < ERROR_LOG_INTERVAL_MS) return;
1475
+ lastErrorLogAt.set(key, now);
1476
+ console.log(message);
1477
+ }
1141
1478
  function createSessionState(sessionId) {
1142
1479
  return {
1143
1480
  sessionId,
@@ -1202,7 +1539,8 @@ function getState(stateRef, ctx) {
1202
1539
  function registerTracing(pi, config, eventShipper, traceShipper) {
1203
1540
  const stateRef = {};
1204
1541
  function logError(hook, err) {
1205
- console.log(
1542
+ rateLimitedErrorLog(
1543
+ hook,
1206
1544
  `[raindrop-ai/pi-agent] [error] Error in ${hook}: ${err instanceof Error ? err.message : String(err)}`
1207
1545
  );
1208
1546
  }
@@ -1255,7 +1593,7 @@ function registerTracing(pi, config, eventShipper, traceShipper) {
1255
1593
  }
1256
1594
  state.toolSpanStarts.clear();
1257
1595
  state.toolCallToLlmParent.clear();
1258
- state.currentInput = event.prompt;
1596
+ state.currentInput = capText2(event.prompt);
1259
1597
  state.turnNumber = 0;
1260
1598
  state.currentSystemPrompt = config.captureSystemPrompt ? truncateSystemPrompt(event.systemPrompt) : void 0;
1261
1599
  state.currentEventRequestId = generateId();
@@ -1276,7 +1614,7 @@ function registerTracing(pi, config, eventShipper, traceShipper) {
1276
1614
  userId: getUserId(state, config.eventMetadata),
1277
1615
  convoId: state.sessionId,
1278
1616
  eventName: getEventName(config),
1279
- input: event.prompt,
1617
+ input: state.currentInput,
1280
1618
  ...attachments.length > 0 ? { attachments } : {},
1281
1619
  properties: getBaseProperties(config, ctx)
1282
1620
  });
@@ -1327,7 +1665,7 @@ function registerTracing(pi, config, eventShipper, traceShipper) {
1327
1665
  const modelId = (_c = message.model) != null ? _c : "";
1328
1666
  const modelName = provider && modelId ? `${provider}/${modelId}` : modelId || "llm";
1329
1667
  const errorForSpan = getAssistantError(message);
1330
- const outputText = getAssistantText(message);
1668
+ const outputText = capText2(getAssistantText(message));
1331
1669
  const inputTokens = (_d = message.usage) == null ? void 0 : _d.input;
1332
1670
  const outputTokens = (_e = message.usage) == null ? void 0 : _e.output;
1333
1671
  const cacheReadTokens = (_f = message.usage) == null ? void 0 : _f.cacheRead;
@@ -1516,12 +1854,14 @@ function extension(pi) {
1516
1854
  writeKey: config.writeKey,
1517
1855
  endpoint: config.endpoint,
1518
1856
  debug: config.debug,
1857
+ projectId: config.projectId,
1519
1858
  localDebuggerUrl: config.localWorkshopUrl
1520
1859
  });
1521
1860
  const traceShipper = new TraceShipper2({
1522
1861
  writeKey: config.writeKey,
1523
1862
  endpoint: config.endpoint,
1524
1863
  debug: config.debug,
1864
+ projectId: config.projectId,
1525
1865
  localDebuggerUrl: config.localWorkshopUrl
1526
1866
  });
1527
1867
  registerTracing(pi, config, eventShipper, traceShipper);