@raindrop-ai/pi-agent 0.0.3 → 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 +13 -4
- package/dist/{chunk-MHIMKMK7.js → chunk-2NGOEVWI.js} +488 -75
- package/dist/extension.cjs +501 -79
- package/dist/extension.d.cts +2 -2
- package/dist/extension.d.ts +2 -2
- package/dist/extension.js +16 -5
- package/dist/index.cjs +491 -78
- package/dist/index.d-CRPiWjXE.d.cts +369 -0
- package/dist/index.d-CRPiWjXE.d.ts +369 -0
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +6 -4
- package/package.json +7 -7
- package/dist/index.d-CtwaEReY.d.cts +0 -228
- package/dist/index.d-CtwaEReY.d.ts +0 -228
|
@@ -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);
|
|
@@ -551,10 +689,113 @@ var EventShipper = class {
|
|
|
551
689
|
}
|
|
552
690
|
}
|
|
553
691
|
};
|
|
692
|
+
var DEFAULT_SECRET_KEY_NAMES = [
|
|
693
|
+
"apikey",
|
|
694
|
+
"apisecret",
|
|
695
|
+
"apitoken",
|
|
696
|
+
"secretaccesskey",
|
|
697
|
+
"sessiontoken",
|
|
698
|
+
"privatekey",
|
|
699
|
+
"privatekeyid",
|
|
700
|
+
"clientsecret",
|
|
701
|
+
"accesstoken",
|
|
702
|
+
"refreshtoken",
|
|
703
|
+
"oauthtoken",
|
|
704
|
+
"bearertoken",
|
|
705
|
+
"authorization",
|
|
706
|
+
"password",
|
|
707
|
+
"passphrase"
|
|
708
|
+
];
|
|
709
|
+
var REDACTED_PLACEHOLDER = "[REDACTED]";
|
|
710
|
+
function normalizeKeyName(name) {
|
|
711
|
+
return name.toLowerCase().replace(/[-_.]/g, "");
|
|
712
|
+
}
|
|
713
|
+
function redactSecretsInObject(value, options) {
|
|
714
|
+
var _a, _b;
|
|
715
|
+
const normalizedSecretSet = buildSecretSet((_a = options == null ? void 0 : options.secretKeyNames) != null ? _a : DEFAULT_SECRET_KEY_NAMES);
|
|
716
|
+
const placeholder = (_b = options == null ? void 0 : options.placeholder) != null ? _b : REDACTED_PLACEHOLDER;
|
|
717
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
718
|
+
const walk = (node) => {
|
|
719
|
+
if (node === null || typeof node !== "object") return node;
|
|
720
|
+
if (seen.has(node)) return "[CIRCULAR]";
|
|
721
|
+
seen.add(node);
|
|
722
|
+
if (Array.isArray(node)) {
|
|
723
|
+
return node.map((item) => walk(item));
|
|
724
|
+
}
|
|
725
|
+
const out = {};
|
|
726
|
+
for (const [k, v] of Object.entries(node)) {
|
|
727
|
+
if (normalizedSecretSet.has(normalizeKeyName(k))) {
|
|
728
|
+
out[k] = placeholder;
|
|
729
|
+
} else {
|
|
730
|
+
out[k] = walk(v);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
return out;
|
|
734
|
+
};
|
|
735
|
+
return walk(value);
|
|
736
|
+
}
|
|
737
|
+
function buildSecretSet(names) {
|
|
738
|
+
const set = /* @__PURE__ */ new Set();
|
|
739
|
+
for (const name of names) set.add(normalizeKeyName(name));
|
|
740
|
+
return set;
|
|
741
|
+
}
|
|
742
|
+
var DEFAULT_REDACT_ATTRIBUTE_KEYS = [
|
|
743
|
+
"ai.request.providerOptions",
|
|
744
|
+
"ai.response.providerMetadata"
|
|
745
|
+
];
|
|
746
|
+
function defaultTransformSpan(span) {
|
|
747
|
+
const attrs = span.attributes;
|
|
748
|
+
if (!attrs || attrs.length === 0) return span;
|
|
749
|
+
let nextAttrs;
|
|
750
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
751
|
+
const attr = attrs[i];
|
|
752
|
+
const redacted = redactJsonAttributeValue(attr.key, attr.value);
|
|
753
|
+
if (redacted === void 0) continue;
|
|
754
|
+
if (!nextAttrs) nextAttrs = attrs.slice();
|
|
755
|
+
nextAttrs[i] = { key: attr.key, value: redacted };
|
|
756
|
+
}
|
|
757
|
+
if (!nextAttrs) return span;
|
|
758
|
+
return { ...span, attributes: nextAttrs };
|
|
759
|
+
}
|
|
760
|
+
var REDACT_JSON_ATTRIBUTE_KEYS = new Set(DEFAULT_REDACT_ATTRIBUTE_KEYS);
|
|
761
|
+
function redactJsonAttributeValue(key, value) {
|
|
762
|
+
if (!REDACT_JSON_ATTRIBUTE_KEYS.has(key)) return void 0;
|
|
763
|
+
const json = value.stringValue;
|
|
764
|
+
if (typeof json !== "string" || json.length === 0) return void 0;
|
|
765
|
+
let parsed;
|
|
766
|
+
try {
|
|
767
|
+
parsed = JSON.parse(json);
|
|
768
|
+
} catch (e) {
|
|
769
|
+
return void 0;
|
|
770
|
+
}
|
|
771
|
+
const scrubbed = redactSecretsInObject(parsed);
|
|
772
|
+
let scrubbedJson;
|
|
773
|
+
try {
|
|
774
|
+
scrubbedJson = JSON.stringify(scrubbed);
|
|
775
|
+
} catch (e) {
|
|
776
|
+
return void 0;
|
|
777
|
+
}
|
|
778
|
+
if (scrubbedJson === json) return void 0;
|
|
779
|
+
return { stringValue: scrubbedJson };
|
|
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
|
+
}
|
|
554
794
|
var TraceShipper = class {
|
|
555
795
|
constructor(opts) {
|
|
556
796
|
this.queue = [];
|
|
557
797
|
this.inFlight = /* @__PURE__ */ new Set();
|
|
798
|
+
this.hasShutdown = false;
|
|
558
799
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
559
800
|
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
560
801
|
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
@@ -572,6 +813,75 @@ var TraceShipper = class {
|
|
|
572
813
|
if (this.debug && this.localDebuggerUrl) {
|
|
573
814
|
console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
|
|
574
815
|
}
|
|
816
|
+
this.transformSpanHook = opts.transformSpan;
|
|
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 };
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Apply the user `transformSpan` hook (if any) followed by the default
|
|
854
|
+
* redactor (unless disabled). Returns either the (possibly new) span to
|
|
855
|
+
* ship, or `null` to drop the span entirely.
|
|
856
|
+
*
|
|
857
|
+
* Ordering: user hook runs first so callers can rewrite the span freely
|
|
858
|
+
* (rename attrs, add new ones, scrub things the default doesn't know
|
|
859
|
+
* about). The default redactor then runs on whatever the user produced,
|
|
860
|
+
* acting as the always-on floor for documented BYOK secrets. If the user
|
|
861
|
+
* sets `disableDefaultRedaction: true`, the floor is skipped.
|
|
862
|
+
*
|
|
863
|
+
* Fail-closed: if the user hook throws, the span is dropped — a buggy
|
|
864
|
+
* hook can never accidentally ship raw, un-redacted spans.
|
|
865
|
+
*/
|
|
866
|
+
redactSpan(span) {
|
|
867
|
+
let current = span;
|
|
868
|
+
if (this.transformSpanHook) {
|
|
869
|
+
try {
|
|
870
|
+
const result = this.transformSpanHook(current);
|
|
871
|
+
if (result === null) return null;
|
|
872
|
+
if (result !== void 0) current = result;
|
|
873
|
+
} catch (err) {
|
|
874
|
+
if (this.debug) {
|
|
875
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
876
|
+
console.warn(`${this.prefix} transformSpan hook threw: ${msg}`);
|
|
877
|
+
}
|
|
878
|
+
return null;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
if (!this.disableDefaultRedaction) {
|
|
882
|
+
current = defaultTransformSpan(current);
|
|
883
|
+
}
|
|
884
|
+
return this.capSpanAttributes(current);
|
|
575
885
|
}
|
|
576
886
|
isDebugEnabled() {
|
|
577
887
|
return this.debug;
|
|
@@ -589,8 +899,8 @@ var TraceShipper = class {
|
|
|
589
899
|
];
|
|
590
900
|
if ((_b = args.attributes) == null ? void 0 : _b.length) attrs.push(...args.attributes);
|
|
591
901
|
const span = { ids, name: args.name, startTimeUnixNano: started, attributes: attrs };
|
|
592
|
-
|
|
593
|
-
|
|
902
|
+
this.mirrorToLocalDebugger(
|
|
903
|
+
buildOtlpSpan({
|
|
594
904
|
ids: span.ids,
|
|
595
905
|
name: span.name,
|
|
596
906
|
startTimeUnixNano: span.startTimeUnixNano,
|
|
@@ -598,16 +908,21 @@ var TraceShipper = class {
|
|
|
598
908
|
// placeholder — will be updated on endSpan
|
|
599
909
|
attributes: span.attributes,
|
|
600
910
|
status: { code: SpanStatusCode.UNSET }
|
|
601
|
-
})
|
|
602
|
-
|
|
603
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
604
|
-
baseUrl: this.localDebuggerUrl,
|
|
605
|
-
debug: false,
|
|
606
|
-
sdkName: this.sdkName
|
|
607
|
-
});
|
|
608
|
-
}
|
|
911
|
+
})
|
|
912
|
+
);
|
|
609
913
|
return span;
|
|
610
914
|
}
|
|
915
|
+
mirrorToLocalDebugger(span) {
|
|
916
|
+
if (!this.localDebuggerUrl) return;
|
|
917
|
+
const redacted = this.redactSpan(span);
|
|
918
|
+
if (redacted === null) return;
|
|
919
|
+
const body = buildExportTraceServiceRequest([redacted], this.serviceName, this.serviceVersion);
|
|
920
|
+
mirrorTraceExportToLocalDebugger(body, {
|
|
921
|
+
baseUrl: this.localDebuggerUrl,
|
|
922
|
+
debug: false,
|
|
923
|
+
sdkName: this.sdkName
|
|
924
|
+
});
|
|
925
|
+
}
|
|
611
926
|
endSpan(span, extra) {
|
|
612
927
|
var _a, _b;
|
|
613
928
|
if (span.endTimeUnixNano) return;
|
|
@@ -629,14 +944,7 @@ var TraceShipper = class {
|
|
|
629
944
|
status
|
|
630
945
|
});
|
|
631
946
|
this.enqueue(otlp);
|
|
632
|
-
|
|
633
|
-
const body = buildExportTraceServiceRequest([otlp], this.serviceName, this.serviceVersion);
|
|
634
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
635
|
-
baseUrl: this.localDebuggerUrl,
|
|
636
|
-
debug: false,
|
|
637
|
-
sdkName: this.sdkName
|
|
638
|
-
});
|
|
639
|
-
}
|
|
947
|
+
this.mirrorToLocalDebugger(otlp);
|
|
640
948
|
}
|
|
641
949
|
createSpan(args) {
|
|
642
950
|
var _a;
|
|
@@ -654,14 +962,7 @@ var TraceShipper = class {
|
|
|
654
962
|
status: args.status
|
|
655
963
|
});
|
|
656
964
|
this.enqueue(otlp);
|
|
657
|
-
|
|
658
|
-
const body = buildExportTraceServiceRequest([otlp], this.serviceName, this.serviceVersion);
|
|
659
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
660
|
-
baseUrl: this.localDebuggerUrl,
|
|
661
|
-
debug: false,
|
|
662
|
-
sdkName: this.sdkName
|
|
663
|
-
});
|
|
664
|
-
}
|
|
965
|
+
this.mirrorToLocalDebugger(otlp);
|
|
665
966
|
}
|
|
666
967
|
enqueue(span) {
|
|
667
968
|
if (!this.enabled) return;
|
|
@@ -673,10 +974,12 @@ var TraceShipper = class {
|
|
|
673
974
|
)}`
|
|
674
975
|
);
|
|
675
976
|
}
|
|
977
|
+
const redacted = this.redactSpan(span);
|
|
978
|
+
if (redacted === null) return;
|
|
676
979
|
if (this.queue.length >= this.maxQueueSize) {
|
|
677
980
|
this.queue.shift();
|
|
678
981
|
}
|
|
679
|
-
this.queue.push(
|
|
982
|
+
this.queue.push(redacted);
|
|
680
983
|
if (this.queue.length >= this.maxBatchSize) {
|
|
681
984
|
void this.flush().catch(() => {
|
|
682
985
|
});
|
|
@@ -699,6 +1002,16 @@ var TraceShipper = class {
|
|
|
699
1002
|
while (this.queue.length > 0) {
|
|
700
1003
|
const batch = this.queue.splice(0, this.maxBatchSize);
|
|
701
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
|
+
}
|
|
702
1015
|
const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
|
|
703
1016
|
const url = `${this.baseUrl}traces`;
|
|
704
1017
|
if (this.debug) {
|
|
@@ -707,11 +1020,7 @@ var TraceShipper = class {
|
|
|
707
1020
|
endpoint: url
|
|
708
1021
|
});
|
|
709
1022
|
}
|
|
710
|
-
const p = postJson(url, body, this.authHeaders(),
|
|
711
|
-
maxAttempts: 3,
|
|
712
|
-
debug: this.debug,
|
|
713
|
-
sdkName: this.sdkName
|
|
714
|
-
});
|
|
1023
|
+
const p = postJson(url, body, this.authHeaders(), opts);
|
|
715
1024
|
this.inFlight.add(p);
|
|
716
1025
|
try {
|
|
717
1026
|
try {
|
|
@@ -719,21 +1028,61 @@ var TraceShipper = class {
|
|
|
719
1028
|
if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
|
|
720
1029
|
} catch (err) {
|
|
721
1030
|
const msg = err instanceof Error ? err.message : String(err);
|
|
722
|
-
|
|
1031
|
+
rateLimitedLog(
|
|
1032
|
+
`${this.prefix}.send_spans_failed`,
|
|
1033
|
+
() => console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`)
|
|
1034
|
+
);
|
|
723
1035
|
}
|
|
724
1036
|
} finally {
|
|
725
1037
|
this.inFlight.delete(p);
|
|
726
1038
|
}
|
|
727
1039
|
}
|
|
728
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
|
+
}
|
|
729
1063
|
async shutdown() {
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
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;
|
|
733
1085
|
}
|
|
734
|
-
await this.flush();
|
|
735
|
-
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
736
|
-
})));
|
|
737
1086
|
}
|
|
738
1087
|
};
|
|
739
1088
|
|
|
@@ -744,7 +1093,7 @@ globalThis.RAINDROP_ASYNC_LOCAL_STORAGE = AsyncLocalStorage;
|
|
|
744
1093
|
// package.json
|
|
745
1094
|
var package_default = {
|
|
746
1095
|
name: "@raindrop-ai/pi-agent",
|
|
747
|
-
version: "0.0.
|
|
1096
|
+
version: "0.0.5",
|
|
748
1097
|
description: "Raindrop observability for Pi Agent \u2014 automatic tracing via subscriber or pi-coding-agent extension",
|
|
749
1098
|
type: "module",
|
|
750
1099
|
license: "MIT",
|
|
@@ -794,18 +1143,18 @@ var package_default = {
|
|
|
794
1143
|
"test:watch": "vitest"
|
|
795
1144
|
},
|
|
796
1145
|
peerDependencies: {
|
|
797
|
-
"@
|
|
798
|
-
"@
|
|
1146
|
+
"@earendil-works/pi-agent-core": ">=0.74.0",
|
|
1147
|
+
"@earendil-works/pi-coding-agent": ">=0.74.0"
|
|
799
1148
|
},
|
|
800
1149
|
peerDependenciesMeta: {
|
|
801
|
-
"@
|
|
1150
|
+
"@earendil-works/pi-coding-agent": {
|
|
802
1151
|
optional: true
|
|
803
1152
|
}
|
|
804
1153
|
},
|
|
805
1154
|
devDependencies: {
|
|
806
1155
|
"@raindrop-ai/core": "workspace:*",
|
|
807
|
-
"@
|
|
808
|
-
"@
|
|
1156
|
+
"@earendil-works/pi-agent-core": "^0.78.0",
|
|
1157
|
+
"@earendil-works/pi-coding-agent": "^0.78.0",
|
|
809
1158
|
"@types/node": "^20.11.17",
|
|
810
1159
|
msw: "^2.12.7",
|
|
811
1160
|
tsup: "^8.5.1",
|
|
@@ -942,19 +1291,82 @@ function formatToolSpanName(toolName, args) {
|
|
|
942
1291
|
}
|
|
943
1292
|
return toolName;
|
|
944
1293
|
}
|
|
945
|
-
|
|
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) {
|
|
946
1358
|
if (value === void 0 || value === null) return void 0;
|
|
947
1359
|
try {
|
|
948
|
-
|
|
1360
|
+
const pruned = boundedClone2(value, { remaining: limit + TRUNCATION_MARKER2.length + 256 }, 0);
|
|
1361
|
+
return JSON.stringify(pruned);
|
|
949
1362
|
} catch (e) {
|
|
950
1363
|
try {
|
|
951
|
-
return String(value);
|
|
1364
|
+
return capText2(String(value), limit);
|
|
952
1365
|
} catch (e2) {
|
|
953
1366
|
return "[unserializable]";
|
|
954
1367
|
}
|
|
955
1368
|
}
|
|
956
1369
|
}
|
|
957
|
-
var MAX_ATTR_LENGTH = 32768;
|
|
958
1370
|
function truncate(value) {
|
|
959
1371
|
if (value === void 0) return void 0;
|
|
960
1372
|
if (value.length <= MAX_ATTR_LENGTH) return value;
|
|
@@ -1000,6 +1412,7 @@ export {
|
|
|
1000
1412
|
extractAssistantText,
|
|
1001
1413
|
extractToolCallIds,
|
|
1002
1414
|
formatToolSpanName,
|
|
1415
|
+
capText2 as capText,
|
|
1003
1416
|
safeStringify,
|
|
1004
1417
|
truncate,
|
|
1005
1418
|
getHostname,
|