@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/README.md +9 -0
- package/dist/{chunk-EWIO36KH.js → chunk-2NGOEVWI.js} +339 -44
- package/dist/extension.cjs +352 -48
- package/dist/extension.d.cts +1 -1
- package/dist/extension.d.ts +1 -1
- package/dist/extension.js +16 -5
- package/dist/index.cjs +342 -47
- package/dist/{index.d-Cxs_NTx0.d.cts → index.d-CRPiWjXE.d.cts} +73 -1
- package/dist/{index.d-Cxs_NTx0.d.ts → index.d-CRPiWjXE.d.ts} +73 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -39,6 +39,15 @@ pi install npm:@raindrop-ai/pi-agent
|
|
|
39
39
|
|
|
40
40
|
Set `RAINDROP_WRITE_KEY` in your environment. Traces appear automatically.
|
|
41
41
|
|
|
42
|
+
## Payload size limits
|
|
43
|
+
|
|
44
|
+
Event input/output are capped at **1,000,000 characters per field** (span
|
|
45
|
+
attributes at 32 KB) and truncated with a `...[truncated by raindrop]` marker.
|
|
46
|
+
The cap is enforced before (or during) serialization, so oversized payloads
|
|
47
|
+
cost the cap — not the payload — on the host's event loop, and large events
|
|
48
|
+
land truncated instead of being rejected at the ingest size limit. Hook error
|
|
49
|
+
logs are rate-limited to one line per failure family per 30s.
|
|
50
|
+
|
|
42
51
|
## Documentation
|
|
43
52
|
|
|
44
53
|
See the full [Pi Agent docs](https://www.raindrop.ai/docs/integrations/pi-agent/) for configuration, per-subscribe overrides, and extension settings.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// ../core/dist/chunk-
|
|
1
|
+
// ../core/dist/chunk-U5HUTMR5.js
|
|
2
2
|
function getCrypto() {
|
|
3
3
|
const c = globalThis.crypto;
|
|
4
4
|
return c;
|
|
@@ -56,6 +56,8 @@ function base64Encode(bytes) {
|
|
|
56
56
|
function generateId() {
|
|
57
57
|
return base64Encode(randomBytes(12)).replace(/[+/=]/g, "").slice(0, 16);
|
|
58
58
|
}
|
|
59
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
60
|
+
var MAX_RETRY_DELAY_MS = 3e4;
|
|
59
61
|
function wait(ms) {
|
|
60
62
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
61
63
|
}
|
|
@@ -63,6 +65,46 @@ function formatEndpoint(endpoint) {
|
|
|
63
65
|
if (!endpoint) return void 0;
|
|
64
66
|
return endpoint.endsWith("/") ? endpoint : `${endpoint}/`;
|
|
65
67
|
}
|
|
68
|
+
function redactUrlForLog(url) {
|
|
69
|
+
try {
|
|
70
|
+
const parsed = new URL(url);
|
|
71
|
+
parsed.username = "";
|
|
72
|
+
parsed.password = "";
|
|
73
|
+
parsed.search = "";
|
|
74
|
+
parsed.hash = "";
|
|
75
|
+
return parsed.toString();
|
|
76
|
+
} catch (e) {
|
|
77
|
+
return "<unparseable-url>";
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
var RATE_LIMITED_LOG_INTERVAL_MS = 3e4;
|
|
81
|
+
var rateLimitedLogLast = /* @__PURE__ */ new Map();
|
|
82
|
+
function rateLimitedLog(key, log) {
|
|
83
|
+
const now = Date.now();
|
|
84
|
+
const last = rateLimitedLogLast.get(key);
|
|
85
|
+
if (last !== void 0 && now - last < RATE_LIMITED_LOG_INTERVAL_MS) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
rateLimitedLogLast.set(key, now);
|
|
89
|
+
log();
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
async function raceWithTimeout(promise, timeoutMs) {
|
|
93
|
+
let timer;
|
|
94
|
+
const settledInTime = await Promise.race([
|
|
95
|
+
promise.then(
|
|
96
|
+
() => true,
|
|
97
|
+
() => true
|
|
98
|
+
),
|
|
99
|
+
new Promise((resolve) => {
|
|
100
|
+
var _a;
|
|
101
|
+
timer = setTimeout(() => resolve(false), Math.max(0, timeoutMs));
|
|
102
|
+
(_a = timer.unref) == null ? void 0 : _a.call(timer);
|
|
103
|
+
})
|
|
104
|
+
]);
|
|
105
|
+
if (timer) clearTimeout(timer);
|
|
106
|
+
return settledInTime;
|
|
107
|
+
}
|
|
66
108
|
function parseRetryAfter(headers) {
|
|
67
109
|
var _a;
|
|
68
110
|
const value = (_a = headers.get("Retry-After")) != null ? _a : headers.get("retry-after");
|
|
@@ -79,12 +121,12 @@ function parseRetryAfter(headers) {
|
|
|
79
121
|
function getRetryDelayMs(attemptNumber, previousError) {
|
|
80
122
|
if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
|
|
81
123
|
const v = previousError.retryAfterMs;
|
|
82
|
-
if (typeof v === "number") return Math.max(0, v);
|
|
124
|
+
if (typeof v === "number") return Math.min(Math.max(0, v), MAX_RETRY_DELAY_MS);
|
|
83
125
|
}
|
|
84
126
|
if (attemptNumber <= 1) return 0;
|
|
85
127
|
const base = 500;
|
|
86
128
|
const factor = Math.pow(2, attemptNumber - 2);
|
|
87
|
-
return base * factor;
|
|
129
|
+
return Math.min(base * factor, MAX_RETRY_DELAY_MS);
|
|
88
130
|
}
|
|
89
131
|
async function withRetry(operation, opName, opts) {
|
|
90
132
|
const prefix = opts.sdkName ? `[raindrop-ai/${opts.sdkName}]` : "[raindrop-ai/core]";
|
|
@@ -119,7 +161,9 @@ async function withRetry(operation, opName, opts) {
|
|
|
119
161
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
120
162
|
}
|
|
121
163
|
async function postJson(url, body, headers, opts) {
|
|
122
|
-
|
|
164
|
+
var _a;
|
|
165
|
+
const opName = `POST ${redactUrlForLog(url)}`;
|
|
166
|
+
const timeoutMs = (_a = opts.timeoutMs) != null ? _a : DEFAULT_REQUEST_TIMEOUT_MS;
|
|
123
167
|
await withRetry(
|
|
124
168
|
async () => {
|
|
125
169
|
const resp = await fetch(url, {
|
|
@@ -128,7 +172,8 @@ async function postJson(url, body, headers, opts) {
|
|
|
128
172
|
"Content-Type": "application/json",
|
|
129
173
|
...headers
|
|
130
174
|
},
|
|
131
|
-
body: JSON.stringify(body)
|
|
175
|
+
body: JSON.stringify(body),
|
|
176
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
132
177
|
});
|
|
133
178
|
if (!resp.ok) {
|
|
134
179
|
const text = await resp.text().catch(() => "");
|
|
@@ -145,6 +190,27 @@ async function postJson(url, body, headers, opts) {
|
|
|
145
190
|
opts
|
|
146
191
|
);
|
|
147
192
|
}
|
|
193
|
+
var DEFAULT_MAX_TEXT_FIELD_CHARS = 1e6;
|
|
194
|
+
var TRUNCATION_MARKER = "...[truncated by raindrop]";
|
|
195
|
+
var currentDefaultMaxTextFieldChars = DEFAULT_MAX_TEXT_FIELD_CHARS;
|
|
196
|
+
function resolveMaxTextFieldChars(value) {
|
|
197
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
198
|
+
return Math.floor(value);
|
|
199
|
+
}
|
|
200
|
+
return currentDefaultMaxTextFieldChars;
|
|
201
|
+
}
|
|
202
|
+
function truncateToLimit(text, limit) {
|
|
203
|
+
if (limit > TRUNCATION_MARKER.length) {
|
|
204
|
+
return text.slice(0, limit - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;
|
|
205
|
+
}
|
|
206
|
+
return text.slice(0, Math.max(0, limit));
|
|
207
|
+
}
|
|
208
|
+
function capText(value, limit) {
|
|
209
|
+
if (typeof value !== "string") return value;
|
|
210
|
+
const max = limit != null ? limit : currentDefaultMaxTextFieldChars;
|
|
211
|
+
if (value.length <= max) return value;
|
|
212
|
+
return truncateToLimit(value, max);
|
|
213
|
+
}
|
|
148
214
|
var SpanStatusCode = {
|
|
149
215
|
UNSET: 0,
|
|
150
216
|
OK: 1,
|
|
@@ -287,6 +353,8 @@ function mirrorPartialEventToLocalDebugger(event, options = {}) {
|
|
|
287
353
|
}).catch(() => {
|
|
288
354
|
});
|
|
289
355
|
}
|
|
356
|
+
var SHUTDOWN_DEADLINE_MS = 1e4;
|
|
357
|
+
var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
290
358
|
function mergePatches(target, source) {
|
|
291
359
|
var _a, _b, _c, _d;
|
|
292
360
|
const out = { ...target, ...source };
|
|
@@ -304,6 +372,7 @@ var EventShipper = class {
|
|
|
304
372
|
this.sticky = /* @__PURE__ */ new Map();
|
|
305
373
|
this.timers = /* @__PURE__ */ new Map();
|
|
306
374
|
this.inFlight = /* @__PURE__ */ new Set();
|
|
375
|
+
this.hasShutdown = false;
|
|
307
376
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
308
377
|
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
309
378
|
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
@@ -313,6 +382,7 @@ var EventShipper = class {
|
|
|
313
382
|
this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
|
|
314
383
|
this.prefix = `[raindrop-ai/${this.sdkName}]`;
|
|
315
384
|
this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
|
|
385
|
+
this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
|
|
316
386
|
this.localDebuggerUrl = (_f = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _f : void 0;
|
|
317
387
|
if (this.debug && this.localDebuggerUrl) {
|
|
318
388
|
console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
|
|
@@ -335,10 +405,52 @@ var EventShipper = class {
|
|
|
335
405
|
authHeaders() {
|
|
336
406
|
return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
|
|
337
407
|
}
|
|
408
|
+
/**
|
|
409
|
+
* Build the retry/timeout options for one POST, honoring the shutdown
|
|
410
|
+
* deadline. Returns `null` when the shutdown drain window is exhausted —
|
|
411
|
+
* the caller must drop the payload (with a rate-limited warning) instead
|
|
412
|
+
* of issuing a request that could outlive process exit.
|
|
413
|
+
*
|
|
414
|
+
* Checked fresh on EVERY send, so a shutdown that begins while the flush
|
|
415
|
+
* path is mid-drain takes effect immediately: no further retries, and the
|
|
416
|
+
* per-attempt timeout is clamped to the remaining window. After
|
|
417
|
+
* `shutdown()` returns (deadline cleared, `hasShutdown` still set),
|
|
418
|
+
* sends — late callers, or flush work the deadline abandoned mid-drain —
|
|
419
|
+
* run as a single short attempt rather than regaining the full retry
|
|
420
|
+
* schedule.
|
|
421
|
+
*/
|
|
422
|
+
requestOpts() {
|
|
423
|
+
if (this.shutdownDeadlineAt !== void 0) {
|
|
424
|
+
const remainingMs = this.shutdownDeadlineAt - Date.now();
|
|
425
|
+
if (remainingMs <= 0) return null;
|
|
426
|
+
return {
|
|
427
|
+
maxAttempts: 1,
|
|
428
|
+
debug: this.debug,
|
|
429
|
+
sdkName: this.sdkName,
|
|
430
|
+
timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
if (this.hasShutdown) {
|
|
434
|
+
return {
|
|
435
|
+
maxAttempts: 1,
|
|
436
|
+
debug: this.debug,
|
|
437
|
+
sdkName: this.sdkName,
|
|
438
|
+
timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
|
|
442
|
+
}
|
|
338
443
|
async patch(eventId, patch) {
|
|
339
444
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
340
445
|
if (!this.enabled) return;
|
|
341
446
|
if (!eventId || !eventId.trim()) return;
|
|
447
|
+
const maxChars = resolveMaxTextFieldChars(this.maxTextFieldCharsOpt);
|
|
448
|
+
if (typeof patch.input === "string" && patch.input.length > maxChars) {
|
|
449
|
+
patch = { ...patch, input: capText(patch.input, maxChars) };
|
|
450
|
+
}
|
|
451
|
+
if (typeof patch.output === "string" && patch.output.length > maxChars) {
|
|
452
|
+
patch = { ...patch, output: capText(patch.output, maxChars) };
|
|
453
|
+
}
|
|
342
454
|
if (this.debug) {
|
|
343
455
|
console.log(`${this.prefix} queue patch`, {
|
|
344
456
|
eventId,
|
|
@@ -385,9 +497,16 @@ var EventShipper = class {
|
|
|
385
497
|
})));
|
|
386
498
|
}
|
|
387
499
|
async shutdown() {
|
|
388
|
-
|
|
389
|
-
this.
|
|
390
|
-
|
|
500
|
+
this.hasShutdown = true;
|
|
501
|
+
this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
|
|
502
|
+
try {
|
|
503
|
+
for (const t of this.timers.values()) clearTimeout(t);
|
|
504
|
+
this.timers.clear();
|
|
505
|
+
const settled = await raceWithTimeout(this.flush(), SHUTDOWN_DEADLINE_MS);
|
|
506
|
+
if (!settled) this.warnShutdownDrop("in-flight request(s) at shutdown");
|
|
507
|
+
} finally {
|
|
508
|
+
this.shutdownDeadlineAt = void 0;
|
|
509
|
+
}
|
|
391
510
|
}
|
|
392
511
|
async trackSignal(signal) {
|
|
393
512
|
var _a, _b;
|
|
@@ -409,15 +528,19 @@ var EventShipper = class {
|
|
|
409
528
|
];
|
|
410
529
|
if (!this.writeKey) return;
|
|
411
530
|
const url = `${this.baseUrl}signals/track`;
|
|
531
|
+
const opts = this.requestOpts();
|
|
532
|
+
if (!opts) {
|
|
533
|
+
this.warnShutdownDrop("signal");
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
412
536
|
try {
|
|
413
|
-
await postJson(url, body, this.authHeaders(),
|
|
414
|
-
maxAttempts: 3,
|
|
415
|
-
debug: this.debug,
|
|
416
|
-
sdkName: this.sdkName
|
|
417
|
-
});
|
|
537
|
+
await postJson(url, body, this.authHeaders(), opts);
|
|
418
538
|
} catch (err) {
|
|
419
539
|
const msg = err instanceof Error ? err.message : String(err);
|
|
420
|
-
|
|
540
|
+
rateLimitedLog(
|
|
541
|
+
`${this.prefix}.send_signal_failed`,
|
|
542
|
+
() => console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`)
|
|
543
|
+
);
|
|
421
544
|
}
|
|
422
545
|
}
|
|
423
546
|
async identify(users) {
|
|
@@ -441,17 +564,27 @@ var EventShipper = class {
|
|
|
441
564
|
if (!this.writeKey) return;
|
|
442
565
|
if (body.length === 0) return;
|
|
443
566
|
const url = `${this.baseUrl}users/identify`;
|
|
567
|
+
const opts = this.requestOpts();
|
|
568
|
+
if (!opts) {
|
|
569
|
+
this.warnShutdownDrop("identify");
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
444
572
|
try {
|
|
445
|
-
await postJson(url, body, this.authHeaders(),
|
|
446
|
-
maxAttempts: 3,
|
|
447
|
-
debug: this.debug,
|
|
448
|
-
sdkName: this.sdkName
|
|
449
|
-
});
|
|
573
|
+
await postJson(url, body, this.authHeaders(), opts);
|
|
450
574
|
} catch (err) {
|
|
451
575
|
const msg = err instanceof Error ? err.message : String(err);
|
|
452
|
-
|
|
576
|
+
rateLimitedLog(
|
|
577
|
+
`${this.prefix}.send_identify_failed`,
|
|
578
|
+
() => console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`)
|
|
579
|
+
);
|
|
453
580
|
}
|
|
454
581
|
}
|
|
582
|
+
warnShutdownDrop(what) {
|
|
583
|
+
rateLimitedLog(
|
|
584
|
+
`${this.prefix}.shutdown_deadline`,
|
|
585
|
+
() => console.warn(`${this.prefix} shutdown flush deadline exceeded; dropping ${what}`)
|
|
586
|
+
);
|
|
587
|
+
}
|
|
455
588
|
async flushOne(eventId) {
|
|
456
589
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
457
590
|
if (!this.enabled) return;
|
|
@@ -527,11 +660,13 @@ var EventShipper = class {
|
|
|
527
660
|
if (!isPending) this.sticky.delete(eventId);
|
|
528
661
|
return;
|
|
529
662
|
}
|
|
530
|
-
const
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
663
|
+
const opts = this.requestOpts();
|
|
664
|
+
if (!opts) {
|
|
665
|
+
this.warnShutdownDrop(`track_partial ${eventId}`);
|
|
666
|
+
if (!isPending) this.sticky.delete(eventId);
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
const p = postJson(url, payload, this.authHeaders(), opts);
|
|
535
670
|
this.inFlight.add(p);
|
|
536
671
|
try {
|
|
537
672
|
try {
|
|
@@ -541,7 +676,10 @@ var EventShipper = class {
|
|
|
541
676
|
}
|
|
542
677
|
} catch (err) {
|
|
543
678
|
const msg = err instanceof Error ? err.message : String(err);
|
|
544
|
-
|
|
679
|
+
rateLimitedLog(
|
|
680
|
+
`${this.prefix}.send_track_partial_failed`,
|
|
681
|
+
() => console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`)
|
|
682
|
+
);
|
|
545
683
|
}
|
|
546
684
|
} finally {
|
|
547
685
|
this.inFlight.delete(p);
|
|
@@ -640,10 +778,24 @@ function redactJsonAttributeValue(key, value) {
|
|
|
640
778
|
if (scrubbedJson === json) return void 0;
|
|
641
779
|
return { stringValue: scrubbedJson };
|
|
642
780
|
}
|
|
781
|
+
function applyOtelSpanAttributeLimit(limit) {
|
|
782
|
+
var _a, _b;
|
|
783
|
+
try {
|
|
784
|
+
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;
|
|
785
|
+
if (!raw) return limit;
|
|
786
|
+
const parsed = Number.parseInt(raw, 10);
|
|
787
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
788
|
+
return Math.min(limit, parsed);
|
|
789
|
+
}
|
|
790
|
+
} catch (e) {
|
|
791
|
+
}
|
|
792
|
+
return limit;
|
|
793
|
+
}
|
|
643
794
|
var TraceShipper = class {
|
|
644
795
|
constructor(opts) {
|
|
645
796
|
this.queue = [];
|
|
646
797
|
this.inFlight = /* @__PURE__ */ new Set();
|
|
798
|
+
this.hasShutdown = false;
|
|
647
799
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
648
800
|
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
649
801
|
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
@@ -663,6 +815,39 @@ var TraceShipper = class {
|
|
|
663
815
|
}
|
|
664
816
|
this.transformSpanHook = opts.transformSpan;
|
|
665
817
|
this.disableDefaultRedaction = opts.disableDefaultRedaction === true;
|
|
818
|
+
this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Cap every string attribute value on the span. O(#attributes) length
|
|
822
|
+
* checks; only oversized values pay a slice. Runs AFTER the redaction
|
|
823
|
+
* pipeline so the default secret-scrub still sees parseable JSON in
|
|
824
|
+
* `ai.request.providerOptions` / `ai.response.providerMetadata` (capping
|
|
825
|
+
* first could cut a JSON blob mid-way, fail the parse, and ship secrets
|
|
826
|
+
* in the surviving prefix).
|
|
827
|
+
*
|
|
828
|
+
* A stricter `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var is honored
|
|
829
|
+
* for span content, matching the Python SDK and the OTel SDK convention.
|
|
830
|
+
*/
|
|
831
|
+
capSpanAttributes(span) {
|
|
832
|
+
var _a;
|
|
833
|
+
const maxChars = applyOtelSpanAttributeLimit(
|
|
834
|
+
resolveMaxTextFieldChars(this.maxTextFieldCharsOpt)
|
|
835
|
+
);
|
|
836
|
+
const attrs = span.attributes;
|
|
837
|
+
if (!attrs || attrs.length === 0) return span;
|
|
838
|
+
let nextAttrs;
|
|
839
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
840
|
+
const attr = attrs[i];
|
|
841
|
+
const value = (_a = attr.value) == null ? void 0 : _a.stringValue;
|
|
842
|
+
if (typeof value !== "string" || value.length <= maxChars) continue;
|
|
843
|
+
if (!nextAttrs) nextAttrs = attrs.slice();
|
|
844
|
+
nextAttrs[i] = {
|
|
845
|
+
key: attr.key,
|
|
846
|
+
value: { ...attr.value, stringValue: capText(value, maxChars) }
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
if (!nextAttrs) return span;
|
|
850
|
+
return { ...span, attributes: nextAttrs };
|
|
666
851
|
}
|
|
667
852
|
/**
|
|
668
853
|
* Apply the user `transformSpan` hook (if any) followed by the default
|
|
@@ -696,7 +881,7 @@ var TraceShipper = class {
|
|
|
696
881
|
if (!this.disableDefaultRedaction) {
|
|
697
882
|
current = defaultTransformSpan(current);
|
|
698
883
|
}
|
|
699
|
-
return current;
|
|
884
|
+
return this.capSpanAttributes(current);
|
|
700
885
|
}
|
|
701
886
|
isDebugEnabled() {
|
|
702
887
|
return this.debug;
|
|
@@ -817,6 +1002,16 @@ var TraceShipper = class {
|
|
|
817
1002
|
while (this.queue.length > 0) {
|
|
818
1003
|
const batch = this.queue.splice(0, this.maxBatchSize);
|
|
819
1004
|
if (!this.writeKey) continue;
|
|
1005
|
+
const opts = this.requestOpts();
|
|
1006
|
+
if (!opts) {
|
|
1007
|
+
rateLimitedLog(
|
|
1008
|
+
`${this.prefix}.shutdown_deadline`,
|
|
1009
|
+
() => console.warn(
|
|
1010
|
+
`${this.prefix} shutdown flush deadline exceeded; dropping ${batch.length} spans`
|
|
1011
|
+
)
|
|
1012
|
+
);
|
|
1013
|
+
continue;
|
|
1014
|
+
}
|
|
820
1015
|
const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
|
|
821
1016
|
const url = `${this.baseUrl}traces`;
|
|
822
1017
|
if (this.debug) {
|
|
@@ -825,11 +1020,7 @@ var TraceShipper = class {
|
|
|
825
1020
|
endpoint: url
|
|
826
1021
|
});
|
|
827
1022
|
}
|
|
828
|
-
const p = postJson(url, body, this.authHeaders(),
|
|
829
|
-
maxAttempts: 3,
|
|
830
|
-
debug: this.debug,
|
|
831
|
-
sdkName: this.sdkName
|
|
832
|
-
});
|
|
1023
|
+
const p = postJson(url, body, this.authHeaders(), opts);
|
|
833
1024
|
this.inFlight.add(p);
|
|
834
1025
|
try {
|
|
835
1026
|
try {
|
|
@@ -837,21 +1028,61 @@ var TraceShipper = class {
|
|
|
837
1028
|
if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
|
|
838
1029
|
} catch (err) {
|
|
839
1030
|
const msg = err instanceof Error ? err.message : String(err);
|
|
840
|
-
|
|
1031
|
+
rateLimitedLog(
|
|
1032
|
+
`${this.prefix}.send_spans_failed`,
|
|
1033
|
+
() => console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`)
|
|
1034
|
+
);
|
|
841
1035
|
}
|
|
842
1036
|
} finally {
|
|
843
1037
|
this.inFlight.delete(p);
|
|
844
1038
|
}
|
|
845
1039
|
}
|
|
846
1040
|
}
|
|
1041
|
+
/** See EventShipper.requestOpts — same shutdown-budget semantics. */
|
|
1042
|
+
requestOpts() {
|
|
1043
|
+
if (this.shutdownDeadlineAt !== void 0) {
|
|
1044
|
+
const remainingMs = this.shutdownDeadlineAt - Date.now();
|
|
1045
|
+
if (remainingMs <= 0) return null;
|
|
1046
|
+
return {
|
|
1047
|
+
maxAttempts: 1,
|
|
1048
|
+
debug: this.debug,
|
|
1049
|
+
sdkName: this.sdkName,
|
|
1050
|
+
timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
if (this.hasShutdown) {
|
|
1054
|
+
return {
|
|
1055
|
+
maxAttempts: 1,
|
|
1056
|
+
debug: this.debug,
|
|
1057
|
+
sdkName: this.sdkName,
|
|
1058
|
+
timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
|
|
1062
|
+
}
|
|
847
1063
|
async shutdown() {
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
1064
|
+
this.hasShutdown = true;
|
|
1065
|
+
this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
|
|
1066
|
+
try {
|
|
1067
|
+
if (this.timer) {
|
|
1068
|
+
clearTimeout(this.timer);
|
|
1069
|
+
this.timer = void 0;
|
|
1070
|
+
}
|
|
1071
|
+
const drain = async () => {
|
|
1072
|
+
await this.flush();
|
|
1073
|
+
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
1074
|
+
})));
|
|
1075
|
+
};
|
|
1076
|
+
const settled = await raceWithTimeout(drain(), SHUTDOWN_DEADLINE_MS);
|
|
1077
|
+
if (!settled) {
|
|
1078
|
+
rateLimitedLog(
|
|
1079
|
+
`${this.prefix}.shutdown_deadline`,
|
|
1080
|
+
() => console.warn(`${this.prefix} shutdown flush deadline exceeded; abandoning in-flight spans`)
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
} finally {
|
|
1084
|
+
this.shutdownDeadlineAt = void 0;
|
|
851
1085
|
}
|
|
852
|
-
await this.flush();
|
|
853
|
-
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
854
|
-
})));
|
|
855
1086
|
}
|
|
856
1087
|
};
|
|
857
1088
|
|
|
@@ -862,7 +1093,7 @@ globalThis.RAINDROP_ASYNC_LOCAL_STORAGE = AsyncLocalStorage;
|
|
|
862
1093
|
// package.json
|
|
863
1094
|
var package_default = {
|
|
864
1095
|
name: "@raindrop-ai/pi-agent",
|
|
865
|
-
version: "0.0.
|
|
1096
|
+
version: "0.0.5",
|
|
866
1097
|
description: "Raindrop observability for Pi Agent \u2014 automatic tracing via subscriber or pi-coding-agent extension",
|
|
867
1098
|
type: "module",
|
|
868
1099
|
license: "MIT",
|
|
@@ -1060,19 +1291,82 @@ function formatToolSpanName(toolName, args) {
|
|
|
1060
1291
|
}
|
|
1061
1292
|
return toolName;
|
|
1062
1293
|
}
|
|
1063
|
-
|
|
1294
|
+
var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
|
|
1295
|
+
var MAX_TEXT_FIELD_CHARS = 1e6;
|
|
1296
|
+
var MAX_ATTR_LENGTH = 32768;
|
|
1297
|
+
var MAX_BOUNDED_DEPTH = 12;
|
|
1298
|
+
function capText2(value, limit = MAX_TEXT_FIELD_CHARS) {
|
|
1299
|
+
if (value.length <= limit) return value;
|
|
1300
|
+
if (limit > TRUNCATION_MARKER2.length) {
|
|
1301
|
+
return value.slice(0, limit - TRUNCATION_MARKER2.length) + TRUNCATION_MARKER2;
|
|
1302
|
+
}
|
|
1303
|
+
return value.slice(0, limit);
|
|
1304
|
+
}
|
|
1305
|
+
function boundedClone2(value, budget, depth) {
|
|
1306
|
+
if (budget.remaining <= 0) return TRUNCATION_MARKER2;
|
|
1307
|
+
if (typeof value === "string") {
|
|
1308
|
+
if (value.length > budget.remaining) {
|
|
1309
|
+
const taken = value.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
|
|
1310
|
+
budget.remaining = 0;
|
|
1311
|
+
return taken;
|
|
1312
|
+
}
|
|
1313
|
+
budget.remaining -= Math.max(value.length, 1);
|
|
1314
|
+
return value;
|
|
1315
|
+
}
|
|
1316
|
+
if (value === null || typeof value === "number" || typeof value === "boolean") {
|
|
1317
|
+
budget.remaining -= 8;
|
|
1318
|
+
return value;
|
|
1319
|
+
}
|
|
1320
|
+
if (typeof value !== "object") {
|
|
1321
|
+
budget.remaining -= 8;
|
|
1322
|
+
return value;
|
|
1323
|
+
}
|
|
1324
|
+
if (depth >= MAX_BOUNDED_DEPTH) {
|
|
1325
|
+
budget.remaining -= 16;
|
|
1326
|
+
return `<max depth: ${TRUNCATION_MARKER2}>`;
|
|
1327
|
+
}
|
|
1328
|
+
const withToJson = value;
|
|
1329
|
+
if (typeof withToJson.toJSON === "function") {
|
|
1330
|
+
try {
|
|
1331
|
+
return boundedClone2(withToJson.toJSON(), budget, depth + 1);
|
|
1332
|
+
} catch (e) {
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
if (Array.isArray(value)) {
|
|
1336
|
+
const out2 = [];
|
|
1337
|
+
for (const item of value) {
|
|
1338
|
+
if (budget.remaining <= 0) {
|
|
1339
|
+
out2.push(TRUNCATION_MARKER2);
|
|
1340
|
+
break;
|
|
1341
|
+
}
|
|
1342
|
+
out2.push(boundedClone2(item, budget, depth + 1));
|
|
1343
|
+
}
|
|
1344
|
+
return out2;
|
|
1345
|
+
}
|
|
1346
|
+
const out = {};
|
|
1347
|
+
for (const key of Object.keys(value)) {
|
|
1348
|
+
if (budget.remaining <= 0) {
|
|
1349
|
+
out["..."] = TRUNCATION_MARKER2;
|
|
1350
|
+
break;
|
|
1351
|
+
}
|
|
1352
|
+
budget.remaining -= Math.max(key.length, 1);
|
|
1353
|
+
out[key] = boundedClone2(value[key], budget, depth + 1);
|
|
1354
|
+
}
|
|
1355
|
+
return out;
|
|
1356
|
+
}
|
|
1357
|
+
function safeStringify(value, limit = MAX_ATTR_LENGTH) {
|
|
1064
1358
|
if (value === void 0 || value === null) return void 0;
|
|
1065
1359
|
try {
|
|
1066
|
-
|
|
1360
|
+
const pruned = boundedClone2(value, { remaining: limit + TRUNCATION_MARKER2.length + 256 }, 0);
|
|
1361
|
+
return JSON.stringify(pruned);
|
|
1067
1362
|
} catch (e) {
|
|
1068
1363
|
try {
|
|
1069
|
-
return String(value);
|
|
1364
|
+
return capText2(String(value), limit);
|
|
1070
1365
|
} catch (e2) {
|
|
1071
1366
|
return "[unserializable]";
|
|
1072
1367
|
}
|
|
1073
1368
|
}
|
|
1074
1369
|
}
|
|
1075
|
-
var MAX_ATTR_LENGTH = 32768;
|
|
1076
1370
|
function truncate(value) {
|
|
1077
1371
|
if (value === void 0) return void 0;
|
|
1078
1372
|
if (value.length <= MAX_ATTR_LENGTH) return value;
|
|
@@ -1118,6 +1412,7 @@ export {
|
|
|
1118
1412
|
extractAssistantText,
|
|
1119
1413
|
extractToolCallIds,
|
|
1120
1414
|
formatToolSpanName,
|
|
1415
|
+
capText2 as capText,
|
|
1121
1416
|
safeStringify,
|
|
1122
1417
|
truncate,
|
|
1123
1418
|
getHostname,
|