@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
package/dist/index.cjs
CHANGED
|
@@ -24,7 +24,7 @@ __export(index_exports, {
|
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(index_exports);
|
|
26
26
|
|
|
27
|
-
// ../core/dist/chunk-
|
|
27
|
+
// ../core/dist/chunk-U5HUTMR5.js
|
|
28
28
|
function getCrypto() {
|
|
29
29
|
const c = globalThis.crypto;
|
|
30
30
|
return c;
|
|
@@ -79,6 +79,8 @@ function base64Encode(bytes) {
|
|
|
79
79
|
}
|
|
80
80
|
return out;
|
|
81
81
|
}
|
|
82
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
83
|
+
var MAX_RETRY_DELAY_MS = 3e4;
|
|
82
84
|
function wait(ms) {
|
|
83
85
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
84
86
|
}
|
|
@@ -86,6 +88,46 @@ function formatEndpoint(endpoint) {
|
|
|
86
88
|
if (!endpoint) return void 0;
|
|
87
89
|
return endpoint.endsWith("/") ? endpoint : `${endpoint}/`;
|
|
88
90
|
}
|
|
91
|
+
function redactUrlForLog(url) {
|
|
92
|
+
try {
|
|
93
|
+
const parsed = new URL(url);
|
|
94
|
+
parsed.username = "";
|
|
95
|
+
parsed.password = "";
|
|
96
|
+
parsed.search = "";
|
|
97
|
+
parsed.hash = "";
|
|
98
|
+
return parsed.toString();
|
|
99
|
+
} catch (e) {
|
|
100
|
+
return "<unparseable-url>";
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
var RATE_LIMITED_LOG_INTERVAL_MS = 3e4;
|
|
104
|
+
var rateLimitedLogLast = /* @__PURE__ */ new Map();
|
|
105
|
+
function rateLimitedLog(key, log) {
|
|
106
|
+
const now = Date.now();
|
|
107
|
+
const last = rateLimitedLogLast.get(key);
|
|
108
|
+
if (last !== void 0 && now - last < RATE_LIMITED_LOG_INTERVAL_MS) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
rateLimitedLogLast.set(key, now);
|
|
112
|
+
log();
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
async function raceWithTimeout(promise, timeoutMs) {
|
|
116
|
+
let timer;
|
|
117
|
+
const settledInTime = await Promise.race([
|
|
118
|
+
promise.then(
|
|
119
|
+
() => true,
|
|
120
|
+
() => true
|
|
121
|
+
),
|
|
122
|
+
new Promise((resolve) => {
|
|
123
|
+
var _a;
|
|
124
|
+
timer = setTimeout(() => resolve(false), Math.max(0, timeoutMs));
|
|
125
|
+
(_a = timer.unref) == null ? void 0 : _a.call(timer);
|
|
126
|
+
})
|
|
127
|
+
]);
|
|
128
|
+
if (timer) clearTimeout(timer);
|
|
129
|
+
return settledInTime;
|
|
130
|
+
}
|
|
89
131
|
function parseRetryAfter(headers) {
|
|
90
132
|
var _a;
|
|
91
133
|
const value = (_a = headers.get("Retry-After")) != null ? _a : headers.get("retry-after");
|
|
@@ -102,12 +144,12 @@ function parseRetryAfter(headers) {
|
|
|
102
144
|
function getRetryDelayMs(attemptNumber, previousError) {
|
|
103
145
|
if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
|
|
104
146
|
const v = previousError.retryAfterMs;
|
|
105
|
-
if (typeof v === "number") return Math.max(0, v);
|
|
147
|
+
if (typeof v === "number") return Math.min(Math.max(0, v), MAX_RETRY_DELAY_MS);
|
|
106
148
|
}
|
|
107
149
|
if (attemptNumber <= 1) return 0;
|
|
108
150
|
const base = 500;
|
|
109
151
|
const factor = Math.pow(2, attemptNumber - 2);
|
|
110
|
-
return base * factor;
|
|
152
|
+
return Math.min(base * factor, MAX_RETRY_DELAY_MS);
|
|
111
153
|
}
|
|
112
154
|
async function withRetry(operation, opName, opts) {
|
|
113
155
|
const prefix = opts.sdkName ? `[raindrop-ai/${opts.sdkName}]` : "[raindrop-ai/core]";
|
|
@@ -142,7 +184,9 @@ async function withRetry(operation, opName, opts) {
|
|
|
142
184
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
143
185
|
}
|
|
144
186
|
async function postJson(url, body, headers, opts) {
|
|
145
|
-
|
|
187
|
+
var _a;
|
|
188
|
+
const opName = `POST ${redactUrlForLog(url)}`;
|
|
189
|
+
const timeoutMs = (_a = opts.timeoutMs) != null ? _a : DEFAULT_REQUEST_TIMEOUT_MS;
|
|
146
190
|
await withRetry(
|
|
147
191
|
async () => {
|
|
148
192
|
const resp = await fetch(url, {
|
|
@@ -151,7 +195,8 @@ async function postJson(url, body, headers, opts) {
|
|
|
151
195
|
"Content-Type": "application/json",
|
|
152
196
|
...headers
|
|
153
197
|
},
|
|
154
|
-
body: JSON.stringify(body)
|
|
198
|
+
body: JSON.stringify(body),
|
|
199
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
155
200
|
});
|
|
156
201
|
if (!resp.ok) {
|
|
157
202
|
const text = await resp.text().catch(() => "");
|
|
@@ -168,6 +213,27 @@ async function postJson(url, body, headers, opts) {
|
|
|
168
213
|
opts
|
|
169
214
|
);
|
|
170
215
|
}
|
|
216
|
+
var DEFAULT_MAX_TEXT_FIELD_CHARS = 1e6;
|
|
217
|
+
var TRUNCATION_MARKER = "...[truncated by raindrop]";
|
|
218
|
+
var currentDefaultMaxTextFieldChars = DEFAULT_MAX_TEXT_FIELD_CHARS;
|
|
219
|
+
function resolveMaxTextFieldChars(value) {
|
|
220
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
221
|
+
return Math.floor(value);
|
|
222
|
+
}
|
|
223
|
+
return currentDefaultMaxTextFieldChars;
|
|
224
|
+
}
|
|
225
|
+
function truncateToLimit(text, limit) {
|
|
226
|
+
if (limit > TRUNCATION_MARKER.length) {
|
|
227
|
+
return text.slice(0, limit - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;
|
|
228
|
+
}
|
|
229
|
+
return text.slice(0, Math.max(0, limit));
|
|
230
|
+
}
|
|
231
|
+
function capText(value, limit) {
|
|
232
|
+
if (typeof value !== "string") return value;
|
|
233
|
+
const max = limit != null ? limit : currentDefaultMaxTextFieldChars;
|
|
234
|
+
if (value.length <= max) return value;
|
|
235
|
+
return truncateToLimit(value, max);
|
|
236
|
+
}
|
|
171
237
|
var SpanStatusCode = {
|
|
172
238
|
UNSET: 0,
|
|
173
239
|
OK: 1,
|
|
@@ -310,6 +376,8 @@ function mirrorPartialEventToLocalDebugger(event, options = {}) {
|
|
|
310
376
|
}).catch(() => {
|
|
311
377
|
});
|
|
312
378
|
}
|
|
379
|
+
var SHUTDOWN_DEADLINE_MS = 1e4;
|
|
380
|
+
var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
313
381
|
function mergePatches(target, source) {
|
|
314
382
|
var _a, _b, _c, _d;
|
|
315
383
|
const out = { ...target, ...source };
|
|
@@ -327,6 +395,7 @@ var EventShipper = class {
|
|
|
327
395
|
this.sticky = /* @__PURE__ */ new Map();
|
|
328
396
|
this.timers = /* @__PURE__ */ new Map();
|
|
329
397
|
this.inFlight = /* @__PURE__ */ new Set();
|
|
398
|
+
this.hasShutdown = false;
|
|
330
399
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
331
400
|
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
332
401
|
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
@@ -336,6 +405,7 @@ var EventShipper = class {
|
|
|
336
405
|
this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
|
|
337
406
|
this.prefix = `[raindrop-ai/${this.sdkName}]`;
|
|
338
407
|
this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
|
|
408
|
+
this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
|
|
339
409
|
this.localDebuggerUrl = (_f = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _f : void 0;
|
|
340
410
|
if (this.debug && this.localDebuggerUrl) {
|
|
341
411
|
console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
|
|
@@ -358,10 +428,52 @@ var EventShipper = class {
|
|
|
358
428
|
authHeaders() {
|
|
359
429
|
return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
|
|
360
430
|
}
|
|
431
|
+
/**
|
|
432
|
+
* Build the retry/timeout options for one POST, honoring the shutdown
|
|
433
|
+
* deadline. Returns `null` when the shutdown drain window is exhausted —
|
|
434
|
+
* the caller must drop the payload (with a rate-limited warning) instead
|
|
435
|
+
* of issuing a request that could outlive process exit.
|
|
436
|
+
*
|
|
437
|
+
* Checked fresh on EVERY send, so a shutdown that begins while the flush
|
|
438
|
+
* path is mid-drain takes effect immediately: no further retries, and the
|
|
439
|
+
* per-attempt timeout is clamped to the remaining window. After
|
|
440
|
+
* `shutdown()` returns (deadline cleared, `hasShutdown` still set),
|
|
441
|
+
* sends — late callers, or flush work the deadline abandoned mid-drain —
|
|
442
|
+
* run as a single short attempt rather than regaining the full retry
|
|
443
|
+
* schedule.
|
|
444
|
+
*/
|
|
445
|
+
requestOpts() {
|
|
446
|
+
if (this.shutdownDeadlineAt !== void 0) {
|
|
447
|
+
const remainingMs = this.shutdownDeadlineAt - Date.now();
|
|
448
|
+
if (remainingMs <= 0) return null;
|
|
449
|
+
return {
|
|
450
|
+
maxAttempts: 1,
|
|
451
|
+
debug: this.debug,
|
|
452
|
+
sdkName: this.sdkName,
|
|
453
|
+
timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
if (this.hasShutdown) {
|
|
457
|
+
return {
|
|
458
|
+
maxAttempts: 1,
|
|
459
|
+
debug: this.debug,
|
|
460
|
+
sdkName: this.sdkName,
|
|
461
|
+
timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
|
|
465
|
+
}
|
|
361
466
|
async patch(eventId, patch) {
|
|
362
467
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
363
468
|
if (!this.enabled) return;
|
|
364
469
|
if (!eventId || !eventId.trim()) return;
|
|
470
|
+
const maxChars = resolveMaxTextFieldChars(this.maxTextFieldCharsOpt);
|
|
471
|
+
if (typeof patch.input === "string" && patch.input.length > maxChars) {
|
|
472
|
+
patch = { ...patch, input: capText(patch.input, maxChars) };
|
|
473
|
+
}
|
|
474
|
+
if (typeof patch.output === "string" && patch.output.length > maxChars) {
|
|
475
|
+
patch = { ...patch, output: capText(patch.output, maxChars) };
|
|
476
|
+
}
|
|
365
477
|
if (this.debug) {
|
|
366
478
|
console.log(`${this.prefix} queue patch`, {
|
|
367
479
|
eventId,
|
|
@@ -408,9 +520,16 @@ var EventShipper = class {
|
|
|
408
520
|
})));
|
|
409
521
|
}
|
|
410
522
|
async shutdown() {
|
|
411
|
-
|
|
412
|
-
this.
|
|
413
|
-
|
|
523
|
+
this.hasShutdown = true;
|
|
524
|
+
this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
|
|
525
|
+
try {
|
|
526
|
+
for (const t of this.timers.values()) clearTimeout(t);
|
|
527
|
+
this.timers.clear();
|
|
528
|
+
const settled = await raceWithTimeout(this.flush(), SHUTDOWN_DEADLINE_MS);
|
|
529
|
+
if (!settled) this.warnShutdownDrop("in-flight request(s) at shutdown");
|
|
530
|
+
} finally {
|
|
531
|
+
this.shutdownDeadlineAt = void 0;
|
|
532
|
+
}
|
|
414
533
|
}
|
|
415
534
|
async trackSignal(signal) {
|
|
416
535
|
var _a, _b;
|
|
@@ -432,15 +551,19 @@ var EventShipper = class {
|
|
|
432
551
|
];
|
|
433
552
|
if (!this.writeKey) return;
|
|
434
553
|
const url = `${this.baseUrl}signals/track`;
|
|
554
|
+
const opts = this.requestOpts();
|
|
555
|
+
if (!opts) {
|
|
556
|
+
this.warnShutdownDrop("signal");
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
435
559
|
try {
|
|
436
|
-
await postJson(url, body, this.authHeaders(),
|
|
437
|
-
maxAttempts: 3,
|
|
438
|
-
debug: this.debug,
|
|
439
|
-
sdkName: this.sdkName
|
|
440
|
-
});
|
|
560
|
+
await postJson(url, body, this.authHeaders(), opts);
|
|
441
561
|
} catch (err) {
|
|
442
562
|
const msg = err instanceof Error ? err.message : String(err);
|
|
443
|
-
|
|
563
|
+
rateLimitedLog(
|
|
564
|
+
`${this.prefix}.send_signal_failed`,
|
|
565
|
+
() => console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`)
|
|
566
|
+
);
|
|
444
567
|
}
|
|
445
568
|
}
|
|
446
569
|
async identify(users) {
|
|
@@ -464,17 +587,27 @@ var EventShipper = class {
|
|
|
464
587
|
if (!this.writeKey) return;
|
|
465
588
|
if (body.length === 0) return;
|
|
466
589
|
const url = `${this.baseUrl}users/identify`;
|
|
590
|
+
const opts = this.requestOpts();
|
|
591
|
+
if (!opts) {
|
|
592
|
+
this.warnShutdownDrop("identify");
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
467
595
|
try {
|
|
468
|
-
await postJson(url, body, this.authHeaders(),
|
|
469
|
-
maxAttempts: 3,
|
|
470
|
-
debug: this.debug,
|
|
471
|
-
sdkName: this.sdkName
|
|
472
|
-
});
|
|
596
|
+
await postJson(url, body, this.authHeaders(), opts);
|
|
473
597
|
} catch (err) {
|
|
474
598
|
const msg = err instanceof Error ? err.message : String(err);
|
|
475
|
-
|
|
599
|
+
rateLimitedLog(
|
|
600
|
+
`${this.prefix}.send_identify_failed`,
|
|
601
|
+
() => console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`)
|
|
602
|
+
);
|
|
476
603
|
}
|
|
477
604
|
}
|
|
605
|
+
warnShutdownDrop(what) {
|
|
606
|
+
rateLimitedLog(
|
|
607
|
+
`${this.prefix}.shutdown_deadline`,
|
|
608
|
+
() => console.warn(`${this.prefix} shutdown flush deadline exceeded; dropping ${what}`)
|
|
609
|
+
);
|
|
610
|
+
}
|
|
478
611
|
async flushOne(eventId) {
|
|
479
612
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
480
613
|
if (!this.enabled) return;
|
|
@@ -550,11 +683,13 @@ var EventShipper = class {
|
|
|
550
683
|
if (!isPending) this.sticky.delete(eventId);
|
|
551
684
|
return;
|
|
552
685
|
}
|
|
553
|
-
const
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
686
|
+
const opts = this.requestOpts();
|
|
687
|
+
if (!opts) {
|
|
688
|
+
this.warnShutdownDrop(`track_partial ${eventId}`);
|
|
689
|
+
if (!isPending) this.sticky.delete(eventId);
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
const p = postJson(url, payload, this.authHeaders(), opts);
|
|
558
693
|
this.inFlight.add(p);
|
|
559
694
|
try {
|
|
560
695
|
try {
|
|
@@ -564,7 +699,10 @@ var EventShipper = class {
|
|
|
564
699
|
}
|
|
565
700
|
} catch (err) {
|
|
566
701
|
const msg = err instanceof Error ? err.message : String(err);
|
|
567
|
-
|
|
702
|
+
rateLimitedLog(
|
|
703
|
+
`${this.prefix}.send_track_partial_failed`,
|
|
704
|
+
() => console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`)
|
|
705
|
+
);
|
|
568
706
|
}
|
|
569
707
|
} finally {
|
|
570
708
|
this.inFlight.delete(p);
|
|
@@ -574,10 +712,113 @@ var EventShipper = class {
|
|
|
574
712
|
}
|
|
575
713
|
}
|
|
576
714
|
};
|
|
715
|
+
var DEFAULT_SECRET_KEY_NAMES = [
|
|
716
|
+
"apikey",
|
|
717
|
+
"apisecret",
|
|
718
|
+
"apitoken",
|
|
719
|
+
"secretaccesskey",
|
|
720
|
+
"sessiontoken",
|
|
721
|
+
"privatekey",
|
|
722
|
+
"privatekeyid",
|
|
723
|
+
"clientsecret",
|
|
724
|
+
"accesstoken",
|
|
725
|
+
"refreshtoken",
|
|
726
|
+
"oauthtoken",
|
|
727
|
+
"bearertoken",
|
|
728
|
+
"authorization",
|
|
729
|
+
"password",
|
|
730
|
+
"passphrase"
|
|
731
|
+
];
|
|
732
|
+
var REDACTED_PLACEHOLDER = "[REDACTED]";
|
|
733
|
+
function normalizeKeyName(name) {
|
|
734
|
+
return name.toLowerCase().replace(/[-_.]/g, "");
|
|
735
|
+
}
|
|
736
|
+
function redactSecretsInObject(value, options) {
|
|
737
|
+
var _a, _b;
|
|
738
|
+
const normalizedSecretSet = buildSecretSet((_a = options == null ? void 0 : options.secretKeyNames) != null ? _a : DEFAULT_SECRET_KEY_NAMES);
|
|
739
|
+
const placeholder = (_b = options == null ? void 0 : options.placeholder) != null ? _b : REDACTED_PLACEHOLDER;
|
|
740
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
741
|
+
const walk = (node) => {
|
|
742
|
+
if (node === null || typeof node !== "object") return node;
|
|
743
|
+
if (seen.has(node)) return "[CIRCULAR]";
|
|
744
|
+
seen.add(node);
|
|
745
|
+
if (Array.isArray(node)) {
|
|
746
|
+
return node.map((item) => walk(item));
|
|
747
|
+
}
|
|
748
|
+
const out = {};
|
|
749
|
+
for (const [k, v] of Object.entries(node)) {
|
|
750
|
+
if (normalizedSecretSet.has(normalizeKeyName(k))) {
|
|
751
|
+
out[k] = placeholder;
|
|
752
|
+
} else {
|
|
753
|
+
out[k] = walk(v);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
return out;
|
|
757
|
+
};
|
|
758
|
+
return walk(value);
|
|
759
|
+
}
|
|
760
|
+
function buildSecretSet(names) {
|
|
761
|
+
const set = /* @__PURE__ */ new Set();
|
|
762
|
+
for (const name of names) set.add(normalizeKeyName(name));
|
|
763
|
+
return set;
|
|
764
|
+
}
|
|
765
|
+
var DEFAULT_REDACT_ATTRIBUTE_KEYS = [
|
|
766
|
+
"ai.request.providerOptions",
|
|
767
|
+
"ai.response.providerMetadata"
|
|
768
|
+
];
|
|
769
|
+
function defaultTransformSpan(span) {
|
|
770
|
+
const attrs = span.attributes;
|
|
771
|
+
if (!attrs || attrs.length === 0) return span;
|
|
772
|
+
let nextAttrs;
|
|
773
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
774
|
+
const attr = attrs[i];
|
|
775
|
+
const redacted = redactJsonAttributeValue(attr.key, attr.value);
|
|
776
|
+
if (redacted === void 0) continue;
|
|
777
|
+
if (!nextAttrs) nextAttrs = attrs.slice();
|
|
778
|
+
nextAttrs[i] = { key: attr.key, value: redacted };
|
|
779
|
+
}
|
|
780
|
+
if (!nextAttrs) return span;
|
|
781
|
+
return { ...span, attributes: nextAttrs };
|
|
782
|
+
}
|
|
783
|
+
var REDACT_JSON_ATTRIBUTE_KEYS = new Set(DEFAULT_REDACT_ATTRIBUTE_KEYS);
|
|
784
|
+
function redactJsonAttributeValue(key, value) {
|
|
785
|
+
if (!REDACT_JSON_ATTRIBUTE_KEYS.has(key)) return void 0;
|
|
786
|
+
const json = value.stringValue;
|
|
787
|
+
if (typeof json !== "string" || json.length === 0) return void 0;
|
|
788
|
+
let parsed;
|
|
789
|
+
try {
|
|
790
|
+
parsed = JSON.parse(json);
|
|
791
|
+
} catch (e) {
|
|
792
|
+
return void 0;
|
|
793
|
+
}
|
|
794
|
+
const scrubbed = redactSecretsInObject(parsed);
|
|
795
|
+
let scrubbedJson;
|
|
796
|
+
try {
|
|
797
|
+
scrubbedJson = JSON.stringify(scrubbed);
|
|
798
|
+
} catch (e) {
|
|
799
|
+
return void 0;
|
|
800
|
+
}
|
|
801
|
+
if (scrubbedJson === json) return void 0;
|
|
802
|
+
return { stringValue: scrubbedJson };
|
|
803
|
+
}
|
|
804
|
+
function applyOtelSpanAttributeLimit(limit) {
|
|
805
|
+
var _a, _b;
|
|
806
|
+
try {
|
|
807
|
+
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;
|
|
808
|
+
if (!raw) return limit;
|
|
809
|
+
const parsed = Number.parseInt(raw, 10);
|
|
810
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
811
|
+
return Math.min(limit, parsed);
|
|
812
|
+
}
|
|
813
|
+
} catch (e) {
|
|
814
|
+
}
|
|
815
|
+
return limit;
|
|
816
|
+
}
|
|
577
817
|
var TraceShipper = class {
|
|
578
818
|
constructor(opts) {
|
|
579
819
|
this.queue = [];
|
|
580
820
|
this.inFlight = /* @__PURE__ */ new Set();
|
|
821
|
+
this.hasShutdown = false;
|
|
581
822
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
582
823
|
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
583
824
|
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
@@ -595,6 +836,75 @@ var TraceShipper = class {
|
|
|
595
836
|
if (this.debug && this.localDebuggerUrl) {
|
|
596
837
|
console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
|
|
597
838
|
}
|
|
839
|
+
this.transformSpanHook = opts.transformSpan;
|
|
840
|
+
this.disableDefaultRedaction = opts.disableDefaultRedaction === true;
|
|
841
|
+
this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Cap every string attribute value on the span. O(#attributes) length
|
|
845
|
+
* checks; only oversized values pay a slice. Runs AFTER the redaction
|
|
846
|
+
* pipeline so the default secret-scrub still sees parseable JSON in
|
|
847
|
+
* `ai.request.providerOptions` / `ai.response.providerMetadata` (capping
|
|
848
|
+
* first could cut a JSON blob mid-way, fail the parse, and ship secrets
|
|
849
|
+
* in the surviving prefix).
|
|
850
|
+
*
|
|
851
|
+
* A stricter `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var is honored
|
|
852
|
+
* for span content, matching the Python SDK and the OTel SDK convention.
|
|
853
|
+
*/
|
|
854
|
+
capSpanAttributes(span) {
|
|
855
|
+
var _a;
|
|
856
|
+
const maxChars = applyOtelSpanAttributeLimit(
|
|
857
|
+
resolveMaxTextFieldChars(this.maxTextFieldCharsOpt)
|
|
858
|
+
);
|
|
859
|
+
const attrs = span.attributes;
|
|
860
|
+
if (!attrs || attrs.length === 0) return span;
|
|
861
|
+
let nextAttrs;
|
|
862
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
863
|
+
const attr = attrs[i];
|
|
864
|
+
const value = (_a = attr.value) == null ? void 0 : _a.stringValue;
|
|
865
|
+
if (typeof value !== "string" || value.length <= maxChars) continue;
|
|
866
|
+
if (!nextAttrs) nextAttrs = attrs.slice();
|
|
867
|
+
nextAttrs[i] = {
|
|
868
|
+
key: attr.key,
|
|
869
|
+
value: { ...attr.value, stringValue: capText(value, maxChars) }
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
if (!nextAttrs) return span;
|
|
873
|
+
return { ...span, attributes: nextAttrs };
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* Apply the user `transformSpan` hook (if any) followed by the default
|
|
877
|
+
* redactor (unless disabled). Returns either the (possibly new) span to
|
|
878
|
+
* ship, or `null` to drop the span entirely.
|
|
879
|
+
*
|
|
880
|
+
* Ordering: user hook runs first so callers can rewrite the span freely
|
|
881
|
+
* (rename attrs, add new ones, scrub things the default doesn't know
|
|
882
|
+
* about). The default redactor then runs on whatever the user produced,
|
|
883
|
+
* acting as the always-on floor for documented BYOK secrets. If the user
|
|
884
|
+
* sets `disableDefaultRedaction: true`, the floor is skipped.
|
|
885
|
+
*
|
|
886
|
+
* Fail-closed: if the user hook throws, the span is dropped — a buggy
|
|
887
|
+
* hook can never accidentally ship raw, un-redacted spans.
|
|
888
|
+
*/
|
|
889
|
+
redactSpan(span) {
|
|
890
|
+
let current = span;
|
|
891
|
+
if (this.transformSpanHook) {
|
|
892
|
+
try {
|
|
893
|
+
const result = this.transformSpanHook(current);
|
|
894
|
+
if (result === null) return null;
|
|
895
|
+
if (result !== void 0) current = result;
|
|
896
|
+
} catch (err) {
|
|
897
|
+
if (this.debug) {
|
|
898
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
899
|
+
console.warn(`${this.prefix} transformSpan hook threw: ${msg}`);
|
|
900
|
+
}
|
|
901
|
+
return null;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
if (!this.disableDefaultRedaction) {
|
|
905
|
+
current = defaultTransformSpan(current);
|
|
906
|
+
}
|
|
907
|
+
return this.capSpanAttributes(current);
|
|
598
908
|
}
|
|
599
909
|
isDebugEnabled() {
|
|
600
910
|
return this.debug;
|
|
@@ -612,8 +922,8 @@ var TraceShipper = class {
|
|
|
612
922
|
];
|
|
613
923
|
if ((_b = args.attributes) == null ? void 0 : _b.length) attrs.push(...args.attributes);
|
|
614
924
|
const span = { ids, name: args.name, startTimeUnixNano: started, attributes: attrs };
|
|
615
|
-
|
|
616
|
-
|
|
925
|
+
this.mirrorToLocalDebugger(
|
|
926
|
+
buildOtlpSpan({
|
|
617
927
|
ids: span.ids,
|
|
618
928
|
name: span.name,
|
|
619
929
|
startTimeUnixNano: span.startTimeUnixNano,
|
|
@@ -621,16 +931,21 @@ var TraceShipper = class {
|
|
|
621
931
|
// placeholder — will be updated on endSpan
|
|
622
932
|
attributes: span.attributes,
|
|
623
933
|
status: { code: SpanStatusCode.UNSET }
|
|
624
|
-
})
|
|
625
|
-
|
|
626
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
627
|
-
baseUrl: this.localDebuggerUrl,
|
|
628
|
-
debug: false,
|
|
629
|
-
sdkName: this.sdkName
|
|
630
|
-
});
|
|
631
|
-
}
|
|
934
|
+
})
|
|
935
|
+
);
|
|
632
936
|
return span;
|
|
633
937
|
}
|
|
938
|
+
mirrorToLocalDebugger(span) {
|
|
939
|
+
if (!this.localDebuggerUrl) return;
|
|
940
|
+
const redacted = this.redactSpan(span);
|
|
941
|
+
if (redacted === null) return;
|
|
942
|
+
const body = buildExportTraceServiceRequest([redacted], this.serviceName, this.serviceVersion);
|
|
943
|
+
mirrorTraceExportToLocalDebugger(body, {
|
|
944
|
+
baseUrl: this.localDebuggerUrl,
|
|
945
|
+
debug: false,
|
|
946
|
+
sdkName: this.sdkName
|
|
947
|
+
});
|
|
948
|
+
}
|
|
634
949
|
endSpan(span, extra) {
|
|
635
950
|
var _a, _b;
|
|
636
951
|
if (span.endTimeUnixNano) return;
|
|
@@ -652,14 +967,7 @@ var TraceShipper = class {
|
|
|
652
967
|
status
|
|
653
968
|
});
|
|
654
969
|
this.enqueue(otlp);
|
|
655
|
-
|
|
656
|
-
const body = buildExportTraceServiceRequest([otlp], this.serviceName, this.serviceVersion);
|
|
657
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
658
|
-
baseUrl: this.localDebuggerUrl,
|
|
659
|
-
debug: false,
|
|
660
|
-
sdkName: this.sdkName
|
|
661
|
-
});
|
|
662
|
-
}
|
|
970
|
+
this.mirrorToLocalDebugger(otlp);
|
|
663
971
|
}
|
|
664
972
|
createSpan(args) {
|
|
665
973
|
var _a;
|
|
@@ -677,14 +985,7 @@ var TraceShipper = class {
|
|
|
677
985
|
status: args.status
|
|
678
986
|
});
|
|
679
987
|
this.enqueue(otlp);
|
|
680
|
-
|
|
681
|
-
const body = buildExportTraceServiceRequest([otlp], this.serviceName, this.serviceVersion);
|
|
682
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
683
|
-
baseUrl: this.localDebuggerUrl,
|
|
684
|
-
debug: false,
|
|
685
|
-
sdkName: this.sdkName
|
|
686
|
-
});
|
|
687
|
-
}
|
|
988
|
+
this.mirrorToLocalDebugger(otlp);
|
|
688
989
|
}
|
|
689
990
|
enqueue(span) {
|
|
690
991
|
if (!this.enabled) return;
|
|
@@ -696,10 +997,12 @@ var TraceShipper = class {
|
|
|
696
997
|
)}`
|
|
697
998
|
);
|
|
698
999
|
}
|
|
1000
|
+
const redacted = this.redactSpan(span);
|
|
1001
|
+
if (redacted === null) return;
|
|
699
1002
|
if (this.queue.length >= this.maxQueueSize) {
|
|
700
1003
|
this.queue.shift();
|
|
701
1004
|
}
|
|
702
|
-
this.queue.push(
|
|
1005
|
+
this.queue.push(redacted);
|
|
703
1006
|
if (this.queue.length >= this.maxBatchSize) {
|
|
704
1007
|
void this.flush().catch(() => {
|
|
705
1008
|
});
|
|
@@ -722,6 +1025,16 @@ var TraceShipper = class {
|
|
|
722
1025
|
while (this.queue.length > 0) {
|
|
723
1026
|
const batch = this.queue.splice(0, this.maxBatchSize);
|
|
724
1027
|
if (!this.writeKey) continue;
|
|
1028
|
+
const opts = this.requestOpts();
|
|
1029
|
+
if (!opts) {
|
|
1030
|
+
rateLimitedLog(
|
|
1031
|
+
`${this.prefix}.shutdown_deadline`,
|
|
1032
|
+
() => console.warn(
|
|
1033
|
+
`${this.prefix} shutdown flush deadline exceeded; dropping ${batch.length} spans`
|
|
1034
|
+
)
|
|
1035
|
+
);
|
|
1036
|
+
continue;
|
|
1037
|
+
}
|
|
725
1038
|
const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
|
|
726
1039
|
const url = `${this.baseUrl}traces`;
|
|
727
1040
|
if (this.debug) {
|
|
@@ -730,11 +1043,7 @@ var TraceShipper = class {
|
|
|
730
1043
|
endpoint: url
|
|
731
1044
|
});
|
|
732
1045
|
}
|
|
733
|
-
const p = postJson(url, body, this.authHeaders(),
|
|
734
|
-
maxAttempts: 3,
|
|
735
|
-
debug: this.debug,
|
|
736
|
-
sdkName: this.sdkName
|
|
737
|
-
});
|
|
1046
|
+
const p = postJson(url, body, this.authHeaders(), opts);
|
|
738
1047
|
this.inFlight.add(p);
|
|
739
1048
|
try {
|
|
740
1049
|
try {
|
|
@@ -742,21 +1051,61 @@ var TraceShipper = class {
|
|
|
742
1051
|
if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
|
|
743
1052
|
} catch (err) {
|
|
744
1053
|
const msg = err instanceof Error ? err.message : String(err);
|
|
745
|
-
|
|
1054
|
+
rateLimitedLog(
|
|
1055
|
+
`${this.prefix}.send_spans_failed`,
|
|
1056
|
+
() => console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`)
|
|
1057
|
+
);
|
|
746
1058
|
}
|
|
747
1059
|
} finally {
|
|
748
1060
|
this.inFlight.delete(p);
|
|
749
1061
|
}
|
|
750
1062
|
}
|
|
751
1063
|
}
|
|
1064
|
+
/** See EventShipper.requestOpts — same shutdown-budget semantics. */
|
|
1065
|
+
requestOpts() {
|
|
1066
|
+
if (this.shutdownDeadlineAt !== void 0) {
|
|
1067
|
+
const remainingMs = this.shutdownDeadlineAt - Date.now();
|
|
1068
|
+
if (remainingMs <= 0) return null;
|
|
1069
|
+
return {
|
|
1070
|
+
maxAttempts: 1,
|
|
1071
|
+
debug: this.debug,
|
|
1072
|
+
sdkName: this.sdkName,
|
|
1073
|
+
timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
if (this.hasShutdown) {
|
|
1077
|
+
return {
|
|
1078
|
+
maxAttempts: 1,
|
|
1079
|
+
debug: this.debug,
|
|
1080
|
+
sdkName: this.sdkName,
|
|
1081
|
+
timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
|
|
1085
|
+
}
|
|
752
1086
|
async shutdown() {
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
1087
|
+
this.hasShutdown = true;
|
|
1088
|
+
this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
|
|
1089
|
+
try {
|
|
1090
|
+
if (this.timer) {
|
|
1091
|
+
clearTimeout(this.timer);
|
|
1092
|
+
this.timer = void 0;
|
|
1093
|
+
}
|
|
1094
|
+
const drain = async () => {
|
|
1095
|
+
await this.flush();
|
|
1096
|
+
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
1097
|
+
})));
|
|
1098
|
+
};
|
|
1099
|
+
const settled = await raceWithTimeout(drain(), SHUTDOWN_DEADLINE_MS);
|
|
1100
|
+
if (!settled) {
|
|
1101
|
+
rateLimitedLog(
|
|
1102
|
+
`${this.prefix}.shutdown_deadline`,
|
|
1103
|
+
() => console.warn(`${this.prefix} shutdown flush deadline exceeded; abandoning in-flight spans`)
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
} finally {
|
|
1107
|
+
this.shutdownDeadlineAt = void 0;
|
|
756
1108
|
}
|
|
757
|
-
await this.flush();
|
|
758
|
-
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
759
|
-
})));
|
|
760
1109
|
}
|
|
761
1110
|
};
|
|
762
1111
|
|
|
@@ -767,7 +1116,7 @@ globalThis.RAINDROP_ASYNC_LOCAL_STORAGE = import_async_hooks.AsyncLocalStorage;
|
|
|
767
1116
|
// package.json
|
|
768
1117
|
var package_default = {
|
|
769
1118
|
name: "@raindrop-ai/pi-agent",
|
|
770
|
-
version: "0.0.
|
|
1119
|
+
version: "0.0.5",
|
|
771
1120
|
description: "Raindrop observability for Pi Agent \u2014 automatic tracing via subscriber or pi-coding-agent extension",
|
|
772
1121
|
type: "module",
|
|
773
1122
|
license: "MIT",
|
|
@@ -817,18 +1166,18 @@ var package_default = {
|
|
|
817
1166
|
"test:watch": "vitest"
|
|
818
1167
|
},
|
|
819
1168
|
peerDependencies: {
|
|
820
|
-
"@
|
|
821
|
-
"@
|
|
1169
|
+
"@earendil-works/pi-agent-core": ">=0.74.0",
|
|
1170
|
+
"@earendil-works/pi-coding-agent": ">=0.74.0"
|
|
822
1171
|
},
|
|
823
1172
|
peerDependenciesMeta: {
|
|
824
|
-
"@
|
|
1173
|
+
"@earendil-works/pi-coding-agent": {
|
|
825
1174
|
optional: true
|
|
826
1175
|
}
|
|
827
1176
|
},
|
|
828
1177
|
devDependencies: {
|
|
829
1178
|
"@raindrop-ai/core": "workspace:*",
|
|
830
|
-
"@
|
|
831
|
-
"@
|
|
1179
|
+
"@earendil-works/pi-agent-core": "^0.78.0",
|
|
1180
|
+
"@earendil-works/pi-coding-agent": "^0.78.0",
|
|
832
1181
|
"@types/node": "^20.11.17",
|
|
833
1182
|
msw: "^2.12.7",
|
|
834
1183
|
tsup: "^8.5.1",
|
|
@@ -965,19 +1314,82 @@ function formatToolSpanName(toolName, args) {
|
|
|
965
1314
|
}
|
|
966
1315
|
return toolName;
|
|
967
1316
|
}
|
|
968
|
-
|
|
1317
|
+
var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
|
|
1318
|
+
var MAX_TEXT_FIELD_CHARS = 1e6;
|
|
1319
|
+
var MAX_ATTR_LENGTH = 32768;
|
|
1320
|
+
var MAX_BOUNDED_DEPTH = 12;
|
|
1321
|
+
function capText2(value, limit = MAX_TEXT_FIELD_CHARS) {
|
|
1322
|
+
if (value.length <= limit) return value;
|
|
1323
|
+
if (limit > TRUNCATION_MARKER2.length) {
|
|
1324
|
+
return value.slice(0, limit - TRUNCATION_MARKER2.length) + TRUNCATION_MARKER2;
|
|
1325
|
+
}
|
|
1326
|
+
return value.slice(0, limit);
|
|
1327
|
+
}
|
|
1328
|
+
function boundedClone2(value, budget, depth) {
|
|
1329
|
+
if (budget.remaining <= 0) return TRUNCATION_MARKER2;
|
|
1330
|
+
if (typeof value === "string") {
|
|
1331
|
+
if (value.length > budget.remaining) {
|
|
1332
|
+
const taken = value.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
|
|
1333
|
+
budget.remaining = 0;
|
|
1334
|
+
return taken;
|
|
1335
|
+
}
|
|
1336
|
+
budget.remaining -= Math.max(value.length, 1);
|
|
1337
|
+
return value;
|
|
1338
|
+
}
|
|
1339
|
+
if (value === null || typeof value === "number" || typeof value === "boolean") {
|
|
1340
|
+
budget.remaining -= 8;
|
|
1341
|
+
return value;
|
|
1342
|
+
}
|
|
1343
|
+
if (typeof value !== "object") {
|
|
1344
|
+
budget.remaining -= 8;
|
|
1345
|
+
return value;
|
|
1346
|
+
}
|
|
1347
|
+
if (depth >= MAX_BOUNDED_DEPTH) {
|
|
1348
|
+
budget.remaining -= 16;
|
|
1349
|
+
return `<max depth: ${TRUNCATION_MARKER2}>`;
|
|
1350
|
+
}
|
|
1351
|
+
const withToJson = value;
|
|
1352
|
+
if (typeof withToJson.toJSON === "function") {
|
|
1353
|
+
try {
|
|
1354
|
+
return boundedClone2(withToJson.toJSON(), budget, depth + 1);
|
|
1355
|
+
} catch (e) {
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
if (Array.isArray(value)) {
|
|
1359
|
+
const out2 = [];
|
|
1360
|
+
for (const item of value) {
|
|
1361
|
+
if (budget.remaining <= 0) {
|
|
1362
|
+
out2.push(TRUNCATION_MARKER2);
|
|
1363
|
+
break;
|
|
1364
|
+
}
|
|
1365
|
+
out2.push(boundedClone2(item, budget, depth + 1));
|
|
1366
|
+
}
|
|
1367
|
+
return out2;
|
|
1368
|
+
}
|
|
1369
|
+
const out = {};
|
|
1370
|
+
for (const key of Object.keys(value)) {
|
|
1371
|
+
if (budget.remaining <= 0) {
|
|
1372
|
+
out["..."] = TRUNCATION_MARKER2;
|
|
1373
|
+
break;
|
|
1374
|
+
}
|
|
1375
|
+
budget.remaining -= Math.max(key.length, 1);
|
|
1376
|
+
out[key] = boundedClone2(value[key], budget, depth + 1);
|
|
1377
|
+
}
|
|
1378
|
+
return out;
|
|
1379
|
+
}
|
|
1380
|
+
function safeStringify(value, limit = MAX_ATTR_LENGTH) {
|
|
969
1381
|
if (value === void 0 || value === null) return void 0;
|
|
970
1382
|
try {
|
|
971
|
-
|
|
1383
|
+
const pruned = boundedClone2(value, { remaining: limit + TRUNCATION_MARKER2.length + 256 }, 0);
|
|
1384
|
+
return JSON.stringify(pruned);
|
|
972
1385
|
} catch (e) {
|
|
973
1386
|
try {
|
|
974
|
-
return String(value);
|
|
1387
|
+
return capText2(String(value), limit);
|
|
975
1388
|
} catch (e2) {
|
|
976
1389
|
return "[unserializable]";
|
|
977
1390
|
}
|
|
978
1391
|
}
|
|
979
1392
|
}
|
|
980
|
-
var MAX_ATTR_LENGTH = 32768;
|
|
981
1393
|
function truncate(value) {
|
|
982
1394
|
if (value === void 0) return void 0;
|
|
983
1395
|
if (value.length <= MAX_ATTR_LENGTH) return value;
|
|
@@ -1094,9 +1506,10 @@ function createSubscriber(agent, eventShipper, traceShipper, defaultOptions, opt
|
|
|
1094
1506
|
if (!currentRun) return;
|
|
1095
1507
|
const userText = extractUserText(message);
|
|
1096
1508
|
if (userText !== void 0) {
|
|
1097
|
-
|
|
1509
|
+
const cappedInput = capText2(userText);
|
|
1510
|
+
currentRun.currentInput = cappedInput;
|
|
1098
1511
|
if (eventShipper) {
|
|
1099
|
-
eventShipper.patch(currentRun.eventId, { input:
|
|
1512
|
+
eventShipper.patch(currentRun.eventId, { input: cappedInput }).catch(() => {
|
|
1100
1513
|
});
|
|
1101
1514
|
}
|
|
1102
1515
|
return;
|
|
@@ -1265,7 +1678,7 @@ function createSubscriber(agent, eventShipper, traceShipper, defaultOptions, opt
|
|
|
1265
1678
|
traceShipper.endSpan(run.currentTurnSpan);
|
|
1266
1679
|
run.currentTurnSpan = void 0;
|
|
1267
1680
|
}
|
|
1268
|
-
const outputText = run.outputParts.join("");
|
|
1681
|
+
const outputText = capText2(run.outputParts.join(""));
|
|
1269
1682
|
if (traceShipper && run.rootSpan) {
|
|
1270
1683
|
const rootAttrs = [
|
|
1271
1684
|
attrString("ai.operationId", "generateText")
|