@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/dist/extension.cjs
CHANGED
|
@@ -25,7 +25,7 @@ __export(extension_exports, {
|
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(extension_exports);
|
|
27
27
|
|
|
28
|
-
// ../core/dist/chunk-
|
|
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
|
-
|
|
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
|
-
|
|
405
|
-
this.
|
|
406
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
865
|
-
|
|
866
|
-
|
|
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
|
-
|
|
915
|
-
|
|
916
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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);
|