@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/README.md +9 -0
- package/dist/{chunk-EWIO36KH.js → chunk-5F37XTG6.js} +372 -44
- package/dist/extension.cjs +392 -52
- package/dist/extension.d.cts +8 -1
- package/dist/extension.d.ts +8 -1
- package/dist/extension.js +23 -9
- package/dist/index.cjs +377 -47
- package/dist/{index.d-Cxs_NTx0.d.cts → index.d-D0J2tEXx.d.cts} +91 -1
- package/dist/{index.d-Cxs_NTx0.d.ts → index.d-D0J2tEXx.d.ts} +91 -1
- package/dist/index.d.cts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +8 -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-SK6EJEO7.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,27 @@ function mirrorPartialEventToLocalDebugger(event, options = {}) {
|
|
|
287
353
|
}).catch(() => {
|
|
288
354
|
});
|
|
289
355
|
}
|
|
356
|
+
var PROJECT_ID_HEADER = "X-Raindrop-Project-Id";
|
|
357
|
+
var PROJECT_ID_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
358
|
+
function isValidProjectIdSlug(value) {
|
|
359
|
+
return PROJECT_ID_SLUG_PATTERN.test(value);
|
|
360
|
+
}
|
|
361
|
+
function normalizeProjectId(raw, opts) {
|
|
362
|
+
if (typeof raw !== "string") return void 0;
|
|
363
|
+
const trimmed = raw.trim();
|
|
364
|
+
if (!trimmed) return void 0;
|
|
365
|
+
if (!isValidProjectIdSlug(trimmed) && opts.debug) {
|
|
366
|
+
console.warn(
|
|
367
|
+
`${opts.prefix} projectId "${trimmed}" does not match slug ${PROJECT_ID_SLUG_PATTERN.source}; sending anyway \u2014 backend may reject with HTTP 400`
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
return trimmed;
|
|
371
|
+
}
|
|
372
|
+
function projectIdHeaders(projectId) {
|
|
373
|
+
return projectId ? { [PROJECT_ID_HEADER]: projectId } : {};
|
|
374
|
+
}
|
|
375
|
+
var SHUTDOWN_DEADLINE_MS = 1e4;
|
|
376
|
+
var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
290
377
|
function mergePatches(target, source) {
|
|
291
378
|
var _a, _b, _c, _d;
|
|
292
379
|
const out = { ...target, ...source };
|
|
@@ -304,6 +391,7 @@ var EventShipper = class {
|
|
|
304
391
|
this.sticky = /* @__PURE__ */ new Map();
|
|
305
392
|
this.timers = /* @__PURE__ */ new Map();
|
|
306
393
|
this.inFlight = /* @__PURE__ */ new Set();
|
|
394
|
+
this.hasShutdown = false;
|
|
307
395
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
308
396
|
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
309
397
|
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
@@ -313,10 +401,15 @@ var EventShipper = class {
|
|
|
313
401
|
this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
|
|
314
402
|
this.prefix = `[raindrop-ai/${this.sdkName}]`;
|
|
315
403
|
this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
|
|
404
|
+
this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
|
|
316
405
|
this.localDebuggerUrl = (_f = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _f : void 0;
|
|
317
406
|
if (this.debug && this.localDebuggerUrl) {
|
|
318
407
|
console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
|
|
319
408
|
}
|
|
409
|
+
this.projectId = normalizeProjectId(opts.projectId, {
|
|
410
|
+
debug: this.debug,
|
|
411
|
+
prefix: this.prefix
|
|
412
|
+
});
|
|
320
413
|
const isNode = typeof process !== "undefined" && typeof process.version === "string";
|
|
321
414
|
this.context = {
|
|
322
415
|
library: {
|
|
@@ -335,10 +428,55 @@ var EventShipper = class {
|
|
|
335
428
|
authHeaders() {
|
|
336
429
|
return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
|
|
337
430
|
}
|
|
431
|
+
requestHeaders() {
|
|
432
|
+
return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Build the retry/timeout options for one POST, honoring the shutdown
|
|
436
|
+
* deadline. Returns `null` when the shutdown drain window is exhausted —
|
|
437
|
+
* the caller must drop the payload (with a rate-limited warning) instead
|
|
438
|
+
* of issuing a request that could outlive process exit.
|
|
439
|
+
*
|
|
440
|
+
* Checked fresh on EVERY send, so a shutdown that begins while the flush
|
|
441
|
+
* path is mid-drain takes effect immediately: no further retries, and the
|
|
442
|
+
* per-attempt timeout is clamped to the remaining window. After
|
|
443
|
+
* `shutdown()` returns (deadline cleared, `hasShutdown` still set),
|
|
444
|
+
* sends — late callers, or flush work the deadline abandoned mid-drain —
|
|
445
|
+
* run as a single short attempt rather than regaining the full retry
|
|
446
|
+
* schedule.
|
|
447
|
+
*/
|
|
448
|
+
requestOpts() {
|
|
449
|
+
if (this.shutdownDeadlineAt !== void 0) {
|
|
450
|
+
const remainingMs = this.shutdownDeadlineAt - Date.now();
|
|
451
|
+
if (remainingMs <= 0) return null;
|
|
452
|
+
return {
|
|
453
|
+
maxAttempts: 1,
|
|
454
|
+
debug: this.debug,
|
|
455
|
+
sdkName: this.sdkName,
|
|
456
|
+
timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
if (this.hasShutdown) {
|
|
460
|
+
return {
|
|
461
|
+
maxAttempts: 1,
|
|
462
|
+
debug: this.debug,
|
|
463
|
+
sdkName: this.sdkName,
|
|
464
|
+
timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
|
|
468
|
+
}
|
|
338
469
|
async patch(eventId, patch) {
|
|
339
470
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
340
471
|
if (!this.enabled) return;
|
|
341
472
|
if (!eventId || !eventId.trim()) return;
|
|
473
|
+
const maxChars = resolveMaxTextFieldChars(this.maxTextFieldCharsOpt);
|
|
474
|
+
if (typeof patch.input === "string" && patch.input.length > maxChars) {
|
|
475
|
+
patch = { ...patch, input: capText(patch.input, maxChars) };
|
|
476
|
+
}
|
|
477
|
+
if (typeof patch.output === "string" && patch.output.length > maxChars) {
|
|
478
|
+
patch = { ...patch, output: capText(patch.output, maxChars) };
|
|
479
|
+
}
|
|
342
480
|
if (this.debug) {
|
|
343
481
|
console.log(`${this.prefix} queue patch`, {
|
|
344
482
|
eventId,
|
|
@@ -385,9 +523,16 @@ var EventShipper = class {
|
|
|
385
523
|
})));
|
|
386
524
|
}
|
|
387
525
|
async shutdown() {
|
|
388
|
-
|
|
389
|
-
this.
|
|
390
|
-
|
|
526
|
+
this.hasShutdown = true;
|
|
527
|
+
this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
|
|
528
|
+
try {
|
|
529
|
+
for (const t of this.timers.values()) clearTimeout(t);
|
|
530
|
+
this.timers.clear();
|
|
531
|
+
const settled = await raceWithTimeout(this.flush(), SHUTDOWN_DEADLINE_MS);
|
|
532
|
+
if (!settled) this.warnShutdownDrop("in-flight request(s) at shutdown");
|
|
533
|
+
} finally {
|
|
534
|
+
this.shutdownDeadlineAt = void 0;
|
|
535
|
+
}
|
|
391
536
|
}
|
|
392
537
|
async trackSignal(signal) {
|
|
393
538
|
var _a, _b;
|
|
@@ -409,15 +554,19 @@ var EventShipper = class {
|
|
|
409
554
|
];
|
|
410
555
|
if (!this.writeKey) return;
|
|
411
556
|
const url = `${this.baseUrl}signals/track`;
|
|
557
|
+
const opts = this.requestOpts();
|
|
558
|
+
if (!opts) {
|
|
559
|
+
this.warnShutdownDrop("signal");
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
412
562
|
try {
|
|
413
|
-
await postJson(url, body, this.
|
|
414
|
-
maxAttempts: 3,
|
|
415
|
-
debug: this.debug,
|
|
416
|
-
sdkName: this.sdkName
|
|
417
|
-
});
|
|
563
|
+
await postJson(url, body, this.requestHeaders(), opts);
|
|
418
564
|
} catch (err) {
|
|
419
565
|
const msg = err instanceof Error ? err.message : String(err);
|
|
420
|
-
|
|
566
|
+
rateLimitedLog(
|
|
567
|
+
`${this.prefix}.send_signal_failed`,
|
|
568
|
+
() => console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`)
|
|
569
|
+
);
|
|
421
570
|
}
|
|
422
571
|
}
|
|
423
572
|
async identify(users) {
|
|
@@ -441,17 +590,27 @@ var EventShipper = class {
|
|
|
441
590
|
if (!this.writeKey) return;
|
|
442
591
|
if (body.length === 0) return;
|
|
443
592
|
const url = `${this.baseUrl}users/identify`;
|
|
593
|
+
const opts = this.requestOpts();
|
|
594
|
+
if (!opts) {
|
|
595
|
+
this.warnShutdownDrop("identify");
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
444
598
|
try {
|
|
445
|
-
await postJson(url, body, this.
|
|
446
|
-
maxAttempts: 3,
|
|
447
|
-
debug: this.debug,
|
|
448
|
-
sdkName: this.sdkName
|
|
449
|
-
});
|
|
599
|
+
await postJson(url, body, this.requestHeaders(), opts);
|
|
450
600
|
} catch (err) {
|
|
451
601
|
const msg = err instanceof Error ? err.message : String(err);
|
|
452
|
-
|
|
602
|
+
rateLimitedLog(
|
|
603
|
+
`${this.prefix}.send_identify_failed`,
|
|
604
|
+
() => console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`)
|
|
605
|
+
);
|
|
453
606
|
}
|
|
454
607
|
}
|
|
608
|
+
warnShutdownDrop(what) {
|
|
609
|
+
rateLimitedLog(
|
|
610
|
+
`${this.prefix}.shutdown_deadline`,
|
|
611
|
+
() => console.warn(`${this.prefix} shutdown flush deadline exceeded; dropping ${what}`)
|
|
612
|
+
);
|
|
613
|
+
}
|
|
455
614
|
async flushOne(eventId) {
|
|
456
615
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
457
616
|
if (!this.enabled) return;
|
|
@@ -527,11 +686,13 @@ var EventShipper = class {
|
|
|
527
686
|
if (!isPending) this.sticky.delete(eventId);
|
|
528
687
|
return;
|
|
529
688
|
}
|
|
530
|
-
const
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
689
|
+
const opts = this.requestOpts();
|
|
690
|
+
if (!opts) {
|
|
691
|
+
this.warnShutdownDrop(`track_partial ${eventId}`);
|
|
692
|
+
if (!isPending) this.sticky.delete(eventId);
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
const p = postJson(url, payload, this.requestHeaders(), opts);
|
|
535
696
|
this.inFlight.add(p);
|
|
536
697
|
try {
|
|
537
698
|
try {
|
|
@@ -541,7 +702,10 @@ var EventShipper = class {
|
|
|
541
702
|
}
|
|
542
703
|
} catch (err) {
|
|
543
704
|
const msg = err instanceof Error ? err.message : String(err);
|
|
544
|
-
|
|
705
|
+
rateLimitedLog(
|
|
706
|
+
`${this.prefix}.send_track_partial_failed`,
|
|
707
|
+
() => console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`)
|
|
708
|
+
);
|
|
545
709
|
}
|
|
546
710
|
} finally {
|
|
547
711
|
this.inFlight.delete(p);
|
|
@@ -640,10 +804,24 @@ function redactJsonAttributeValue(key, value) {
|
|
|
640
804
|
if (scrubbedJson === json) return void 0;
|
|
641
805
|
return { stringValue: scrubbedJson };
|
|
642
806
|
}
|
|
807
|
+
function applyOtelSpanAttributeLimit(limit) {
|
|
808
|
+
var _a, _b;
|
|
809
|
+
try {
|
|
810
|
+
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;
|
|
811
|
+
if (!raw) return limit;
|
|
812
|
+
const parsed = Number.parseInt(raw, 10);
|
|
813
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
814
|
+
return Math.min(limit, parsed);
|
|
815
|
+
}
|
|
816
|
+
} catch (e) {
|
|
817
|
+
}
|
|
818
|
+
return limit;
|
|
819
|
+
}
|
|
643
820
|
var TraceShipper = class {
|
|
644
821
|
constructor(opts) {
|
|
645
822
|
this.queue = [];
|
|
646
823
|
this.inFlight = /* @__PURE__ */ new Set();
|
|
824
|
+
this.hasShutdown = false;
|
|
647
825
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
648
826
|
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
649
827
|
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
@@ -661,8 +839,45 @@ var TraceShipper = class {
|
|
|
661
839
|
if (this.debug && this.localDebuggerUrl) {
|
|
662
840
|
console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
|
|
663
841
|
}
|
|
842
|
+
this.projectId = normalizeProjectId(opts.projectId, {
|
|
843
|
+
debug: this.debug,
|
|
844
|
+
prefix: this.prefix
|
|
845
|
+
});
|
|
664
846
|
this.transformSpanHook = opts.transformSpan;
|
|
665
847
|
this.disableDefaultRedaction = opts.disableDefaultRedaction === true;
|
|
848
|
+
this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Cap every string attribute value on the span. O(#attributes) length
|
|
852
|
+
* checks; only oversized values pay a slice. Runs AFTER the redaction
|
|
853
|
+
* pipeline so the default secret-scrub still sees parseable JSON in
|
|
854
|
+
* `ai.request.providerOptions` / `ai.response.providerMetadata` (capping
|
|
855
|
+
* first could cut a JSON blob mid-way, fail the parse, and ship secrets
|
|
856
|
+
* in the surviving prefix).
|
|
857
|
+
*
|
|
858
|
+
* A stricter `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var is honored
|
|
859
|
+
* for span content, matching the Python SDK and the OTel SDK convention.
|
|
860
|
+
*/
|
|
861
|
+
capSpanAttributes(span) {
|
|
862
|
+
var _a;
|
|
863
|
+
const maxChars = applyOtelSpanAttributeLimit(
|
|
864
|
+
resolveMaxTextFieldChars(this.maxTextFieldCharsOpt)
|
|
865
|
+
);
|
|
866
|
+
const attrs = span.attributes;
|
|
867
|
+
if (!attrs || attrs.length === 0) return span;
|
|
868
|
+
let nextAttrs;
|
|
869
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
870
|
+
const attr = attrs[i];
|
|
871
|
+
const value = (_a = attr.value) == null ? void 0 : _a.stringValue;
|
|
872
|
+
if (typeof value !== "string" || value.length <= maxChars) continue;
|
|
873
|
+
if (!nextAttrs) nextAttrs = attrs.slice();
|
|
874
|
+
nextAttrs[i] = {
|
|
875
|
+
key: attr.key,
|
|
876
|
+
value: { ...attr.value, stringValue: capText(value, maxChars) }
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
if (!nextAttrs) return span;
|
|
880
|
+
return { ...span, attributes: nextAttrs };
|
|
666
881
|
}
|
|
667
882
|
/**
|
|
668
883
|
* Apply the user `transformSpan` hook (if any) followed by the default
|
|
@@ -696,7 +911,7 @@ var TraceShipper = class {
|
|
|
696
911
|
if (!this.disableDefaultRedaction) {
|
|
697
912
|
current = defaultTransformSpan(current);
|
|
698
913
|
}
|
|
699
|
-
return current;
|
|
914
|
+
return this.capSpanAttributes(current);
|
|
700
915
|
}
|
|
701
916
|
isDebugEnabled() {
|
|
702
917
|
return this.debug;
|
|
@@ -704,6 +919,9 @@ var TraceShipper = class {
|
|
|
704
919
|
authHeaders() {
|
|
705
920
|
return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
|
|
706
921
|
}
|
|
922
|
+
requestHeaders() {
|
|
923
|
+
return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
|
|
924
|
+
}
|
|
707
925
|
startSpan(args) {
|
|
708
926
|
var _a, _b;
|
|
709
927
|
const ids = createSpanIds(args.parent);
|
|
@@ -817,6 +1035,16 @@ var TraceShipper = class {
|
|
|
817
1035
|
while (this.queue.length > 0) {
|
|
818
1036
|
const batch = this.queue.splice(0, this.maxBatchSize);
|
|
819
1037
|
if (!this.writeKey) continue;
|
|
1038
|
+
const opts = this.requestOpts();
|
|
1039
|
+
if (!opts) {
|
|
1040
|
+
rateLimitedLog(
|
|
1041
|
+
`${this.prefix}.shutdown_deadline`,
|
|
1042
|
+
() => console.warn(
|
|
1043
|
+
`${this.prefix} shutdown flush deadline exceeded; dropping ${batch.length} spans`
|
|
1044
|
+
)
|
|
1045
|
+
);
|
|
1046
|
+
continue;
|
|
1047
|
+
}
|
|
820
1048
|
const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
|
|
821
1049
|
const url = `${this.baseUrl}traces`;
|
|
822
1050
|
if (this.debug) {
|
|
@@ -825,11 +1053,7 @@ var TraceShipper = class {
|
|
|
825
1053
|
endpoint: url
|
|
826
1054
|
});
|
|
827
1055
|
}
|
|
828
|
-
const p = postJson(url, body, this.
|
|
829
|
-
maxAttempts: 3,
|
|
830
|
-
debug: this.debug,
|
|
831
|
-
sdkName: this.sdkName
|
|
832
|
-
});
|
|
1056
|
+
const p = postJson(url, body, this.requestHeaders(), opts);
|
|
833
1057
|
this.inFlight.add(p);
|
|
834
1058
|
try {
|
|
835
1059
|
try {
|
|
@@ -837,21 +1061,61 @@ var TraceShipper = class {
|
|
|
837
1061
|
if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
|
|
838
1062
|
} catch (err) {
|
|
839
1063
|
const msg = err instanceof Error ? err.message : String(err);
|
|
840
|
-
|
|
1064
|
+
rateLimitedLog(
|
|
1065
|
+
`${this.prefix}.send_spans_failed`,
|
|
1066
|
+
() => console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`)
|
|
1067
|
+
);
|
|
841
1068
|
}
|
|
842
1069
|
} finally {
|
|
843
1070
|
this.inFlight.delete(p);
|
|
844
1071
|
}
|
|
845
1072
|
}
|
|
846
1073
|
}
|
|
1074
|
+
/** See EventShipper.requestOpts — same shutdown-budget semantics. */
|
|
1075
|
+
requestOpts() {
|
|
1076
|
+
if (this.shutdownDeadlineAt !== void 0) {
|
|
1077
|
+
const remainingMs = this.shutdownDeadlineAt - Date.now();
|
|
1078
|
+
if (remainingMs <= 0) return null;
|
|
1079
|
+
return {
|
|
1080
|
+
maxAttempts: 1,
|
|
1081
|
+
debug: this.debug,
|
|
1082
|
+
sdkName: this.sdkName,
|
|
1083
|
+
timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
if (this.hasShutdown) {
|
|
1087
|
+
return {
|
|
1088
|
+
maxAttempts: 1,
|
|
1089
|
+
debug: this.debug,
|
|
1090
|
+
sdkName: this.sdkName,
|
|
1091
|
+
timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
|
|
1095
|
+
}
|
|
847
1096
|
async shutdown() {
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
1097
|
+
this.hasShutdown = true;
|
|
1098
|
+
this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
|
|
1099
|
+
try {
|
|
1100
|
+
if (this.timer) {
|
|
1101
|
+
clearTimeout(this.timer);
|
|
1102
|
+
this.timer = void 0;
|
|
1103
|
+
}
|
|
1104
|
+
const drain = async () => {
|
|
1105
|
+
await this.flush();
|
|
1106
|
+
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
1107
|
+
})));
|
|
1108
|
+
};
|
|
1109
|
+
const settled = await raceWithTimeout(drain(), SHUTDOWN_DEADLINE_MS);
|
|
1110
|
+
if (!settled) {
|
|
1111
|
+
rateLimitedLog(
|
|
1112
|
+
`${this.prefix}.shutdown_deadline`,
|
|
1113
|
+
() => console.warn(`${this.prefix} shutdown flush deadline exceeded; abandoning in-flight spans`)
|
|
1114
|
+
);
|
|
1115
|
+
}
|
|
1116
|
+
} finally {
|
|
1117
|
+
this.shutdownDeadlineAt = void 0;
|
|
851
1118
|
}
|
|
852
|
-
await this.flush();
|
|
853
|
-
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
854
|
-
})));
|
|
855
1119
|
}
|
|
856
1120
|
};
|
|
857
1121
|
|
|
@@ -862,7 +1126,7 @@ globalThis.RAINDROP_ASYNC_LOCAL_STORAGE = AsyncLocalStorage;
|
|
|
862
1126
|
// package.json
|
|
863
1127
|
var package_default = {
|
|
864
1128
|
name: "@raindrop-ai/pi-agent",
|
|
865
|
-
version: "0.0.
|
|
1129
|
+
version: "0.0.6",
|
|
866
1130
|
description: "Raindrop observability for Pi Agent \u2014 automatic tracing via subscriber or pi-coding-agent extension",
|
|
867
1131
|
type: "module",
|
|
868
1132
|
license: "MIT",
|
|
@@ -1060,19 +1324,82 @@ function formatToolSpanName(toolName, args) {
|
|
|
1060
1324
|
}
|
|
1061
1325
|
return toolName;
|
|
1062
1326
|
}
|
|
1063
|
-
|
|
1327
|
+
var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
|
|
1328
|
+
var MAX_TEXT_FIELD_CHARS = 1e6;
|
|
1329
|
+
var MAX_ATTR_LENGTH = 32768;
|
|
1330
|
+
var MAX_BOUNDED_DEPTH = 12;
|
|
1331
|
+
function capText2(value, limit = MAX_TEXT_FIELD_CHARS) {
|
|
1332
|
+
if (value.length <= limit) return value;
|
|
1333
|
+
if (limit > TRUNCATION_MARKER2.length) {
|
|
1334
|
+
return value.slice(0, limit - TRUNCATION_MARKER2.length) + TRUNCATION_MARKER2;
|
|
1335
|
+
}
|
|
1336
|
+
return value.slice(0, limit);
|
|
1337
|
+
}
|
|
1338
|
+
function boundedClone2(value, budget, depth) {
|
|
1339
|
+
if (budget.remaining <= 0) return TRUNCATION_MARKER2;
|
|
1340
|
+
if (typeof value === "string") {
|
|
1341
|
+
if (value.length > budget.remaining) {
|
|
1342
|
+
const taken = value.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
|
|
1343
|
+
budget.remaining = 0;
|
|
1344
|
+
return taken;
|
|
1345
|
+
}
|
|
1346
|
+
budget.remaining -= Math.max(value.length, 1);
|
|
1347
|
+
return value;
|
|
1348
|
+
}
|
|
1349
|
+
if (value === null || typeof value === "number" || typeof value === "boolean") {
|
|
1350
|
+
budget.remaining -= 8;
|
|
1351
|
+
return value;
|
|
1352
|
+
}
|
|
1353
|
+
if (typeof value !== "object") {
|
|
1354
|
+
budget.remaining -= 8;
|
|
1355
|
+
return value;
|
|
1356
|
+
}
|
|
1357
|
+
if (depth >= MAX_BOUNDED_DEPTH) {
|
|
1358
|
+
budget.remaining -= 16;
|
|
1359
|
+
return `<max depth: ${TRUNCATION_MARKER2}>`;
|
|
1360
|
+
}
|
|
1361
|
+
const withToJson = value;
|
|
1362
|
+
if (typeof withToJson.toJSON === "function") {
|
|
1363
|
+
try {
|
|
1364
|
+
return boundedClone2(withToJson.toJSON(), budget, depth + 1);
|
|
1365
|
+
} catch (e) {
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
if (Array.isArray(value)) {
|
|
1369
|
+
const out2 = [];
|
|
1370
|
+
for (const item of value) {
|
|
1371
|
+
if (budget.remaining <= 0) {
|
|
1372
|
+
out2.push(TRUNCATION_MARKER2);
|
|
1373
|
+
break;
|
|
1374
|
+
}
|
|
1375
|
+
out2.push(boundedClone2(item, budget, depth + 1));
|
|
1376
|
+
}
|
|
1377
|
+
return out2;
|
|
1378
|
+
}
|
|
1379
|
+
const out = {};
|
|
1380
|
+
for (const key of Object.keys(value)) {
|
|
1381
|
+
if (budget.remaining <= 0) {
|
|
1382
|
+
out["..."] = TRUNCATION_MARKER2;
|
|
1383
|
+
break;
|
|
1384
|
+
}
|
|
1385
|
+
budget.remaining -= Math.max(key.length, 1);
|
|
1386
|
+
out[key] = boundedClone2(value[key], budget, depth + 1);
|
|
1387
|
+
}
|
|
1388
|
+
return out;
|
|
1389
|
+
}
|
|
1390
|
+
function safeStringify(value, limit = MAX_ATTR_LENGTH) {
|
|
1064
1391
|
if (value === void 0 || value === null) return void 0;
|
|
1065
1392
|
try {
|
|
1066
|
-
|
|
1393
|
+
const pruned = boundedClone2(value, { remaining: limit + TRUNCATION_MARKER2.length + 256 }, 0);
|
|
1394
|
+
return JSON.stringify(pruned);
|
|
1067
1395
|
} catch (e) {
|
|
1068
1396
|
try {
|
|
1069
|
-
return String(value);
|
|
1397
|
+
return capText2(String(value), limit);
|
|
1070
1398
|
} catch (e2) {
|
|
1071
1399
|
return "[unserializable]";
|
|
1072
1400
|
}
|
|
1073
1401
|
}
|
|
1074
1402
|
}
|
|
1075
|
-
var MAX_ATTR_LENGTH = 32768;
|
|
1076
1403
|
function truncate(value) {
|
|
1077
1404
|
if (value === void 0) return void 0;
|
|
1078
1405
|
if (value.length <= MAX_ATTR_LENGTH) return value;
|
|
@@ -1118,6 +1445,7 @@ export {
|
|
|
1118
1445
|
extractAssistantText,
|
|
1119
1446
|
extractToolCallIds,
|
|
1120
1447
|
formatToolSpanName,
|
|
1448
|
+
capText2 as capText,
|
|
1121
1449
|
safeStringify,
|
|
1122
1450
|
truncate,
|
|
1123
1451
|
getHostname,
|