@raindrop-ai/claude-code 0.0.13 → 0.0.14
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/LICENSE +21 -0
- package/README.md +11 -0
- package/dist/cli.js +650 -104
- package/dist/index.cjs +602 -100
- package/dist/index.d.cts +155 -2
- package/dist/index.d.ts +155 -2
- package/dist/index.js +602 -100
- package/package.json +22 -13
package/dist/index.cjs
CHANGED
|
@@ -43,7 +43,7 @@ __export(src_exports, {
|
|
|
43
43
|
});
|
|
44
44
|
module.exports = __toCommonJS(src_exports);
|
|
45
45
|
|
|
46
|
-
// ../core/dist/chunk-
|
|
46
|
+
// ../core/dist/chunk-U5HUTMR5.js
|
|
47
47
|
function getCrypto() {
|
|
48
48
|
const c = globalThis.crypto;
|
|
49
49
|
return c;
|
|
@@ -87,6 +87,8 @@ function base64Encode(bytes) {
|
|
|
87
87
|
}
|
|
88
88
|
return out;
|
|
89
89
|
}
|
|
90
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
91
|
+
var MAX_RETRY_DELAY_MS = 3e4;
|
|
90
92
|
function wait(ms) {
|
|
91
93
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
92
94
|
}
|
|
@@ -94,6 +96,46 @@ function formatEndpoint(endpoint) {
|
|
|
94
96
|
if (!endpoint) return void 0;
|
|
95
97
|
return endpoint.endsWith("/") ? endpoint : `${endpoint}/`;
|
|
96
98
|
}
|
|
99
|
+
function redactUrlForLog(url) {
|
|
100
|
+
try {
|
|
101
|
+
const parsed = new URL(url);
|
|
102
|
+
parsed.username = "";
|
|
103
|
+
parsed.password = "";
|
|
104
|
+
parsed.search = "";
|
|
105
|
+
parsed.hash = "";
|
|
106
|
+
return parsed.toString();
|
|
107
|
+
} catch (e) {
|
|
108
|
+
return "<unparseable-url>";
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
var RATE_LIMITED_LOG_INTERVAL_MS = 3e4;
|
|
112
|
+
var rateLimitedLogLast = /* @__PURE__ */ new Map();
|
|
113
|
+
function rateLimitedLog(key, log) {
|
|
114
|
+
const now = Date.now();
|
|
115
|
+
const last = rateLimitedLogLast.get(key);
|
|
116
|
+
if (last !== void 0 && now - last < RATE_LIMITED_LOG_INTERVAL_MS) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
rateLimitedLogLast.set(key, now);
|
|
120
|
+
log();
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
async function raceWithTimeout(promise, timeoutMs) {
|
|
124
|
+
let timer;
|
|
125
|
+
const settledInTime = await Promise.race([
|
|
126
|
+
promise.then(
|
|
127
|
+
() => true,
|
|
128
|
+
() => true
|
|
129
|
+
),
|
|
130
|
+
new Promise((resolve) => {
|
|
131
|
+
var _a;
|
|
132
|
+
timer = setTimeout(() => resolve(false), Math.max(0, timeoutMs));
|
|
133
|
+
(_a = timer.unref) == null ? void 0 : _a.call(timer);
|
|
134
|
+
})
|
|
135
|
+
]);
|
|
136
|
+
if (timer) clearTimeout(timer);
|
|
137
|
+
return settledInTime;
|
|
138
|
+
}
|
|
97
139
|
function parseRetryAfter(headers) {
|
|
98
140
|
var _a;
|
|
99
141
|
const value = (_a = headers.get("Retry-After")) != null ? _a : headers.get("retry-after");
|
|
@@ -110,12 +152,12 @@ function parseRetryAfter(headers) {
|
|
|
110
152
|
function getRetryDelayMs(attemptNumber, previousError) {
|
|
111
153
|
if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
|
|
112
154
|
const v = previousError.retryAfterMs;
|
|
113
|
-
if (typeof v === "number") return Math.max(0, v);
|
|
155
|
+
if (typeof v === "number") return Math.min(Math.max(0, v), MAX_RETRY_DELAY_MS);
|
|
114
156
|
}
|
|
115
157
|
if (attemptNumber <= 1) return 0;
|
|
116
158
|
const base = 500;
|
|
117
159
|
const factor = Math.pow(2, attemptNumber - 2);
|
|
118
|
-
return base * factor;
|
|
160
|
+
return Math.min(base * factor, MAX_RETRY_DELAY_MS);
|
|
119
161
|
}
|
|
120
162
|
async function withRetry(operation, opName, opts) {
|
|
121
163
|
const prefix = opts.sdkName ? `[raindrop-ai/${opts.sdkName}]` : "[raindrop-ai/core]";
|
|
@@ -150,7 +192,9 @@ async function withRetry(operation, opName, opts) {
|
|
|
150
192
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
151
193
|
}
|
|
152
194
|
async function postJson(url, body, headers, opts) {
|
|
153
|
-
|
|
195
|
+
var _a;
|
|
196
|
+
const opName = `POST ${redactUrlForLog(url)}`;
|
|
197
|
+
const timeoutMs = (_a = opts.timeoutMs) != null ? _a : DEFAULT_REQUEST_TIMEOUT_MS;
|
|
154
198
|
await withRetry(
|
|
155
199
|
async () => {
|
|
156
200
|
const resp = await fetch(url, {
|
|
@@ -159,7 +203,8 @@ async function postJson(url, body, headers, opts) {
|
|
|
159
203
|
"Content-Type": "application/json",
|
|
160
204
|
...headers
|
|
161
205
|
},
|
|
162
|
-
body: JSON.stringify(body)
|
|
206
|
+
body: JSON.stringify(body),
|
|
207
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
163
208
|
});
|
|
164
209
|
if (!resp.ok) {
|
|
165
210
|
const text = await resp.text().catch(() => "");
|
|
@@ -176,6 +221,27 @@ async function postJson(url, body, headers, opts) {
|
|
|
176
221
|
opts
|
|
177
222
|
);
|
|
178
223
|
}
|
|
224
|
+
var DEFAULT_MAX_TEXT_FIELD_CHARS = 1e6;
|
|
225
|
+
var TRUNCATION_MARKER = "...[truncated by raindrop]";
|
|
226
|
+
var currentDefaultMaxTextFieldChars = DEFAULT_MAX_TEXT_FIELD_CHARS;
|
|
227
|
+
function resolveMaxTextFieldChars(value) {
|
|
228
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
229
|
+
return Math.floor(value);
|
|
230
|
+
}
|
|
231
|
+
return currentDefaultMaxTextFieldChars;
|
|
232
|
+
}
|
|
233
|
+
function truncateToLimit(text, limit) {
|
|
234
|
+
if (limit > TRUNCATION_MARKER.length) {
|
|
235
|
+
return text.slice(0, limit - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;
|
|
236
|
+
}
|
|
237
|
+
return text.slice(0, Math.max(0, limit));
|
|
238
|
+
}
|
|
239
|
+
function capText(value, limit) {
|
|
240
|
+
if (typeof value !== "string") return value;
|
|
241
|
+
const max = limit != null ? limit : currentDefaultMaxTextFieldChars;
|
|
242
|
+
if (value.length <= max) return value;
|
|
243
|
+
return truncateToLimit(value, max);
|
|
244
|
+
}
|
|
179
245
|
var SpanStatusCode = {
|
|
180
246
|
UNSET: 0,
|
|
181
247
|
OK: 1,
|
|
@@ -233,6 +299,93 @@ function buildExportTraceServiceRequest(spans, serviceName = "raindrop.core", se
|
|
|
233
299
|
]
|
|
234
300
|
};
|
|
235
301
|
}
|
|
302
|
+
var LOCAL_DEBUGGER_ENV_VAR = "RAINDROP_LOCAL_DEBUGGER";
|
|
303
|
+
var WORKSHOP_ENV_VAR = "RAINDROP_WORKSHOP";
|
|
304
|
+
var DEFAULT_LOCAL_WORKSHOP_URL = "http://localhost:5899/v1/";
|
|
305
|
+
function readEnvVar(name) {
|
|
306
|
+
var _a;
|
|
307
|
+
try {
|
|
308
|
+
const env = (_a = globalThis == null ? void 0 : globalThis.process) == null ? void 0 : _a.env;
|
|
309
|
+
if (env && typeof env[name] === "string" && env[name].length > 0) {
|
|
310
|
+
return env[name];
|
|
311
|
+
}
|
|
312
|
+
} catch (e) {
|
|
313
|
+
}
|
|
314
|
+
return void 0;
|
|
315
|
+
}
|
|
316
|
+
function readWorkshopEnv() {
|
|
317
|
+
const raw = readEnvVar(WORKSHOP_ENV_VAR);
|
|
318
|
+
if (raw === void 0) return void 0;
|
|
319
|
+
const trimmed = raw.trim();
|
|
320
|
+
if (trimmed.length === 0) return void 0;
|
|
321
|
+
if (/^https?:\/\//i.test(trimmed)) return { url: trimmed };
|
|
322
|
+
if (/^(1|true|yes|on)$/i.test(trimmed)) return "enable";
|
|
323
|
+
if (/^(0|false|no|off)$/i.test(trimmed)) return "disable";
|
|
324
|
+
return void 0;
|
|
325
|
+
}
|
|
326
|
+
function isLocalDevHost(hostname) {
|
|
327
|
+
if (!hostname) return false;
|
|
328
|
+
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "0.0.0.0" || hostname === "::1") {
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
if (hostname.endsWith(".localhost")) return true;
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
function readRuntimeHostname() {
|
|
335
|
+
try {
|
|
336
|
+
const loc = globalThis == null ? void 0 : globalThis.location;
|
|
337
|
+
if (loc && typeof loc.hostname === "string" && loc.hostname.length > 0) {
|
|
338
|
+
return loc.hostname;
|
|
339
|
+
}
|
|
340
|
+
} catch (e) {
|
|
341
|
+
}
|
|
342
|
+
return void 0;
|
|
343
|
+
}
|
|
344
|
+
function shouldAutoEnableLocalWorkshop() {
|
|
345
|
+
if (isLocalDevHost(readRuntimeHostname())) return true;
|
|
346
|
+
if (readEnvVar("NODE_ENV") === "development") return true;
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
function resolveLocalDebuggerBaseUrl(baseUrl) {
|
|
350
|
+
var _a, _b, _c;
|
|
351
|
+
if (baseUrl === null) return null;
|
|
352
|
+
if (typeof baseUrl === "string" && baseUrl.length > 0) {
|
|
353
|
+
return (_a = formatEndpoint(baseUrl)) != null ? _a : null;
|
|
354
|
+
}
|
|
355
|
+
const explicitUrlEnv = readEnvVar(LOCAL_DEBUGGER_ENV_VAR);
|
|
356
|
+
if (explicitUrlEnv) return (_b = formatEndpoint(explicitUrlEnv)) != null ? _b : null;
|
|
357
|
+
const workshopEnv = readWorkshopEnv();
|
|
358
|
+
if (workshopEnv === "disable") return null;
|
|
359
|
+
if (workshopEnv === "enable") return DEFAULT_LOCAL_WORKSHOP_URL;
|
|
360
|
+
if (workshopEnv && "url" in workshopEnv) return (_c = formatEndpoint(workshopEnv.url)) != null ? _c : null;
|
|
361
|
+
if (shouldAutoEnableLocalWorkshop()) return DEFAULT_LOCAL_WORKSHOP_URL;
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
function mirrorTraceExportToLocalDebugger(body, options = {}) {
|
|
365
|
+
var _a;
|
|
366
|
+
const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
|
|
367
|
+
if (!baseUrl) return;
|
|
368
|
+
void postJson(`${baseUrl}traces`, body, {}, {
|
|
369
|
+
maxAttempts: 1,
|
|
370
|
+
debug: (_a = options.debug) != null ? _a : false,
|
|
371
|
+
sdkName: options.sdkName
|
|
372
|
+
}).catch(() => {
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
function mirrorPartialEventToLocalDebugger(event, options = {}) {
|
|
376
|
+
var _a;
|
|
377
|
+
const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
|
|
378
|
+
if (!baseUrl) return;
|
|
379
|
+
const headers = options.writeKey ? { Authorization: `Bearer ${options.writeKey}` } : {};
|
|
380
|
+
void postJson(`${baseUrl}events/track_partial`, event, headers, {
|
|
381
|
+
maxAttempts: 1,
|
|
382
|
+
debug: (_a = options.debug) != null ? _a : false,
|
|
383
|
+
sdkName: options.sdkName
|
|
384
|
+
}).catch(() => {
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
var SHUTDOWN_DEADLINE_MS = 1e4;
|
|
388
|
+
var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
236
389
|
function mergePatches(target, source) {
|
|
237
390
|
var _a, _b, _c, _d;
|
|
238
391
|
const out = { ...target, ...source };
|
|
@@ -250,7 +403,8 @@ var EventShipper = class {
|
|
|
250
403
|
this.sticky = /* @__PURE__ */ new Map();
|
|
251
404
|
this.timers = /* @__PURE__ */ new Map();
|
|
252
405
|
this.inFlight = /* @__PURE__ */ new Set();
|
|
253
|
-
|
|
406
|
+
this.hasShutdown = false;
|
|
407
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
254
408
|
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
255
409
|
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
256
410
|
this.enabled = opts.enabled !== false;
|
|
@@ -259,11 +413,16 @@ var EventShipper = class {
|
|
|
259
413
|
this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
|
|
260
414
|
this.prefix = `[raindrop-ai/${this.sdkName}]`;
|
|
261
415
|
this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
|
|
416
|
+
this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
|
|
417
|
+
this.localDebuggerUrl = (_f = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _f : void 0;
|
|
418
|
+
if (this.debug && this.localDebuggerUrl) {
|
|
419
|
+
console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
|
|
420
|
+
}
|
|
262
421
|
const isNode = typeof process !== "undefined" && typeof process.version === "string";
|
|
263
422
|
this.context = {
|
|
264
423
|
library: {
|
|
265
|
-
name: (
|
|
266
|
-
version: (
|
|
424
|
+
name: (_g = opts.libraryName) != null ? _g : "@raindrop-ai/core",
|
|
425
|
+
version: (_h = opts.libraryVersion) != null ? _h : "0.0.0"
|
|
267
426
|
},
|
|
268
427
|
metadata: {
|
|
269
428
|
jsRuntime: isNode ? "node" : "web",
|
|
@@ -277,10 +436,52 @@ var EventShipper = class {
|
|
|
277
436
|
authHeaders() {
|
|
278
437
|
return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
|
|
279
438
|
}
|
|
439
|
+
/**
|
|
440
|
+
* Build the retry/timeout options for one POST, honoring the shutdown
|
|
441
|
+
* deadline. Returns `null` when the shutdown drain window is exhausted —
|
|
442
|
+
* the caller must drop the payload (with a rate-limited warning) instead
|
|
443
|
+
* of issuing a request that could outlive process exit.
|
|
444
|
+
*
|
|
445
|
+
* Checked fresh on EVERY send, so a shutdown that begins while the flush
|
|
446
|
+
* path is mid-drain takes effect immediately: no further retries, and the
|
|
447
|
+
* per-attempt timeout is clamped to the remaining window. After
|
|
448
|
+
* `shutdown()` returns (deadline cleared, `hasShutdown` still set),
|
|
449
|
+
* sends — late callers, or flush work the deadline abandoned mid-drain —
|
|
450
|
+
* run as a single short attempt rather than regaining the full retry
|
|
451
|
+
* schedule.
|
|
452
|
+
*/
|
|
453
|
+
requestOpts() {
|
|
454
|
+
if (this.shutdownDeadlineAt !== void 0) {
|
|
455
|
+
const remainingMs = this.shutdownDeadlineAt - Date.now();
|
|
456
|
+
if (remainingMs <= 0) return null;
|
|
457
|
+
return {
|
|
458
|
+
maxAttempts: 1,
|
|
459
|
+
debug: this.debug,
|
|
460
|
+
sdkName: this.sdkName,
|
|
461
|
+
timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
if (this.hasShutdown) {
|
|
465
|
+
return {
|
|
466
|
+
maxAttempts: 1,
|
|
467
|
+
debug: this.debug,
|
|
468
|
+
sdkName: this.sdkName,
|
|
469
|
+
timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
|
|
473
|
+
}
|
|
280
474
|
async patch(eventId, patch) {
|
|
281
475
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
282
476
|
if (!this.enabled) return;
|
|
283
477
|
if (!eventId || !eventId.trim()) return;
|
|
478
|
+
const maxChars = resolveMaxTextFieldChars(this.maxTextFieldCharsOpt);
|
|
479
|
+
if (typeof patch.input === "string" && patch.input.length > maxChars) {
|
|
480
|
+
patch = { ...patch, input: capText(patch.input, maxChars) };
|
|
481
|
+
}
|
|
482
|
+
if (typeof patch.output === "string" && patch.output.length > maxChars) {
|
|
483
|
+
patch = { ...patch, output: capText(patch.output, maxChars) };
|
|
484
|
+
}
|
|
284
485
|
if (this.debug) {
|
|
285
486
|
console.log(`${this.prefix} queue patch`, {
|
|
286
487
|
eventId,
|
|
@@ -327,9 +528,16 @@ var EventShipper = class {
|
|
|
327
528
|
})));
|
|
328
529
|
}
|
|
329
530
|
async shutdown() {
|
|
330
|
-
|
|
331
|
-
this.
|
|
332
|
-
|
|
531
|
+
this.hasShutdown = true;
|
|
532
|
+
this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
|
|
533
|
+
try {
|
|
534
|
+
for (const t of this.timers.values()) clearTimeout(t);
|
|
535
|
+
this.timers.clear();
|
|
536
|
+
const settled = await raceWithTimeout(this.flush(), SHUTDOWN_DEADLINE_MS);
|
|
537
|
+
if (!settled) this.warnShutdownDrop("in-flight request(s) at shutdown");
|
|
538
|
+
} finally {
|
|
539
|
+
this.shutdownDeadlineAt = void 0;
|
|
540
|
+
}
|
|
333
541
|
}
|
|
334
542
|
async trackSignal(signal) {
|
|
335
543
|
var _a, _b;
|
|
@@ -349,16 +557,21 @@ var EventShipper = class {
|
|
|
349
557
|
}
|
|
350
558
|
}
|
|
351
559
|
];
|
|
560
|
+
if (!this.writeKey) return;
|
|
352
561
|
const url = `${this.baseUrl}signals/track`;
|
|
562
|
+
const opts = this.requestOpts();
|
|
563
|
+
if (!opts) {
|
|
564
|
+
this.warnShutdownDrop("signal");
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
353
567
|
try {
|
|
354
|
-
await postJson(url, body, this.authHeaders(),
|
|
355
|
-
maxAttempts: 3,
|
|
356
|
-
debug: this.debug,
|
|
357
|
-
sdkName: this.sdkName
|
|
358
|
-
});
|
|
568
|
+
await postJson(url, body, this.authHeaders(), opts);
|
|
359
569
|
} catch (err) {
|
|
360
570
|
const msg = err instanceof Error ? err.message : String(err);
|
|
361
|
-
|
|
571
|
+
rateLimitedLog(
|
|
572
|
+
`${this.prefix}.send_signal_failed`,
|
|
573
|
+
() => console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`)
|
|
574
|
+
);
|
|
362
575
|
}
|
|
363
576
|
}
|
|
364
577
|
async identify(users) {
|
|
@@ -379,19 +592,30 @@ var EventShipper = class {
|
|
|
379
592
|
traits: (_a = user.traits) != null ? _a : {}
|
|
380
593
|
};
|
|
381
594
|
});
|
|
595
|
+
if (!this.writeKey) return;
|
|
382
596
|
if (body.length === 0) return;
|
|
383
597
|
const url = `${this.baseUrl}users/identify`;
|
|
598
|
+
const opts = this.requestOpts();
|
|
599
|
+
if (!opts) {
|
|
600
|
+
this.warnShutdownDrop("identify");
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
384
603
|
try {
|
|
385
|
-
await postJson(url, body, this.authHeaders(),
|
|
386
|
-
maxAttempts: 3,
|
|
387
|
-
debug: this.debug,
|
|
388
|
-
sdkName: this.sdkName
|
|
389
|
-
});
|
|
604
|
+
await postJson(url, body, this.authHeaders(), opts);
|
|
390
605
|
} catch (err) {
|
|
391
606
|
const msg = err instanceof Error ? err.message : String(err);
|
|
392
|
-
|
|
607
|
+
rateLimitedLog(
|
|
608
|
+
`${this.prefix}.send_identify_failed`,
|
|
609
|
+
() => console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`)
|
|
610
|
+
);
|
|
393
611
|
}
|
|
394
612
|
}
|
|
613
|
+
warnShutdownDrop(what) {
|
|
614
|
+
rateLimitedLog(
|
|
615
|
+
`${this.prefix}.shutdown_deadline`,
|
|
616
|
+
() => console.warn(`${this.prefix} shutdown flush deadline exceeded; dropping ${what}`)
|
|
617
|
+
);
|
|
618
|
+
}
|
|
395
619
|
async flushOne(eventId) {
|
|
396
620
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
397
621
|
if (!this.enabled) return;
|
|
@@ -455,11 +679,25 @@ var EventShipper = class {
|
|
|
455
679
|
endpoint: url
|
|
456
680
|
});
|
|
457
681
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
682
|
+
if (this.localDebuggerUrl) {
|
|
683
|
+
mirrorPartialEventToLocalDebugger(payload, {
|
|
684
|
+
baseUrl: this.localDebuggerUrl,
|
|
685
|
+
writeKey: this.writeKey,
|
|
686
|
+
debug: this.debug,
|
|
687
|
+
sdkName: this.sdkName
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
if (!this.writeKey) {
|
|
691
|
+
if (!isPending) this.sticky.delete(eventId);
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
const opts = this.requestOpts();
|
|
695
|
+
if (!opts) {
|
|
696
|
+
this.warnShutdownDrop(`track_partial ${eventId}`);
|
|
697
|
+
if (!isPending) this.sticky.delete(eventId);
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
const p = postJson(url, payload, this.authHeaders(), opts);
|
|
463
701
|
this.inFlight.add(p);
|
|
464
702
|
try {
|
|
465
703
|
try {
|
|
@@ -469,7 +707,10 @@ var EventShipper = class {
|
|
|
469
707
|
}
|
|
470
708
|
} catch (err) {
|
|
471
709
|
const msg = err instanceof Error ? err.message : String(err);
|
|
472
|
-
|
|
710
|
+
rateLimitedLog(
|
|
711
|
+
`${this.prefix}.send_track_partial_failed`,
|
|
712
|
+
() => console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`)
|
|
713
|
+
);
|
|
473
714
|
}
|
|
474
715
|
} finally {
|
|
475
716
|
this.inFlight.delete(p);
|
|
@@ -479,28 +720,114 @@ var EventShipper = class {
|
|
|
479
720
|
}
|
|
480
721
|
}
|
|
481
722
|
};
|
|
482
|
-
var
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
723
|
+
var DEFAULT_SECRET_KEY_NAMES = [
|
|
724
|
+
"apikey",
|
|
725
|
+
"apisecret",
|
|
726
|
+
"apitoken",
|
|
727
|
+
"secretaccesskey",
|
|
728
|
+
"sessiontoken",
|
|
729
|
+
"privatekey",
|
|
730
|
+
"privatekeyid",
|
|
731
|
+
"clientsecret",
|
|
732
|
+
"accesstoken",
|
|
733
|
+
"refreshtoken",
|
|
734
|
+
"oauthtoken",
|
|
735
|
+
"bearertoken",
|
|
736
|
+
"authorization",
|
|
737
|
+
"password",
|
|
738
|
+
"passphrase"
|
|
739
|
+
];
|
|
740
|
+
var REDACTED_PLACEHOLDER = "[REDACTED]";
|
|
741
|
+
function normalizeKeyName(name) {
|
|
742
|
+
return name.toLowerCase().replace(/[-_.]/g, "");
|
|
743
|
+
}
|
|
744
|
+
function redactSecretsInObject(value, options) {
|
|
745
|
+
var _a, _b;
|
|
746
|
+
const normalizedSecretSet = buildSecretSet((_a = options == null ? void 0 : options.secretKeyNames) != null ? _a : DEFAULT_SECRET_KEY_NAMES);
|
|
747
|
+
const placeholder = (_b = options == null ? void 0 : options.placeholder) != null ? _b : REDACTED_PLACEHOLDER;
|
|
748
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
749
|
+
const walk = (node) => {
|
|
750
|
+
if (node === null || typeof node !== "object") return node;
|
|
751
|
+
if (seen.has(node)) return "[CIRCULAR]";
|
|
752
|
+
seen.add(node);
|
|
753
|
+
if (Array.isArray(node)) {
|
|
754
|
+
return node.map((item) => walk(item));
|
|
755
|
+
}
|
|
756
|
+
const out = {};
|
|
757
|
+
for (const [k, v] of Object.entries(node)) {
|
|
758
|
+
if (normalizedSecretSet.has(normalizeKeyName(k))) {
|
|
759
|
+
out[k] = placeholder;
|
|
760
|
+
} else {
|
|
761
|
+
out[k] = walk(v);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
return out;
|
|
765
|
+
};
|
|
766
|
+
return walk(value);
|
|
767
|
+
}
|
|
768
|
+
function buildSecretSet(names) {
|
|
769
|
+
const set = /* @__PURE__ */ new Set();
|
|
770
|
+
for (const name of names) set.add(normalizeKeyName(name));
|
|
771
|
+
return set;
|
|
772
|
+
}
|
|
773
|
+
var DEFAULT_REDACT_ATTRIBUTE_KEYS = [
|
|
774
|
+
"ai.request.providerOptions",
|
|
775
|
+
"ai.response.providerMetadata"
|
|
776
|
+
];
|
|
777
|
+
function defaultTransformSpan(span) {
|
|
778
|
+
const attrs = span.attributes;
|
|
779
|
+
if (!attrs || attrs.length === 0) return span;
|
|
780
|
+
let nextAttrs;
|
|
781
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
782
|
+
const attr = attrs[i];
|
|
783
|
+
const redacted = redactJsonAttributeValue(attr.key, attr.value);
|
|
784
|
+
if (redacted === void 0) continue;
|
|
785
|
+
if (!nextAttrs) nextAttrs = attrs.slice();
|
|
786
|
+
nextAttrs[i] = { key: attr.key, value: redacted };
|
|
787
|
+
}
|
|
788
|
+
if (!nextAttrs) return span;
|
|
789
|
+
return { ...span, attributes: nextAttrs };
|
|
790
|
+
}
|
|
791
|
+
var REDACT_JSON_ATTRIBUTE_KEYS = new Set(DEFAULT_REDACT_ATTRIBUTE_KEYS);
|
|
792
|
+
function redactJsonAttributeValue(key, value) {
|
|
793
|
+
if (!REDACT_JSON_ATTRIBUTE_KEYS.has(key)) return void 0;
|
|
794
|
+
const json = value.stringValue;
|
|
795
|
+
if (typeof json !== "string" || json.length === 0) return void 0;
|
|
796
|
+
let parsed;
|
|
797
|
+
try {
|
|
798
|
+
parsed = JSON.parse(json);
|
|
799
|
+
} catch (e) {
|
|
800
|
+
return void 0;
|
|
801
|
+
}
|
|
802
|
+
const scrubbed = redactSecretsInObject(parsed);
|
|
803
|
+
let scrubbedJson;
|
|
804
|
+
try {
|
|
805
|
+
scrubbedJson = JSON.stringify(scrubbed);
|
|
806
|
+
} catch (e) {
|
|
807
|
+
return void 0;
|
|
808
|
+
}
|
|
809
|
+
if (scrubbedJson === json) return void 0;
|
|
810
|
+
return { stringValue: scrubbedJson };
|
|
487
811
|
}
|
|
488
|
-
function
|
|
489
|
-
var _a;
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
})
|
|
812
|
+
function applyOtelSpanAttributeLimit(limit) {
|
|
813
|
+
var _a, _b;
|
|
814
|
+
try {
|
|
815
|
+
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;
|
|
816
|
+
if (!raw) return limit;
|
|
817
|
+
const parsed = Number.parseInt(raw, 10);
|
|
818
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
819
|
+
return Math.min(limit, parsed);
|
|
820
|
+
}
|
|
821
|
+
} catch (e) {
|
|
822
|
+
}
|
|
823
|
+
return limit;
|
|
498
824
|
}
|
|
499
825
|
var TraceShipper = class {
|
|
500
826
|
constructor(opts) {
|
|
501
827
|
this.queue = [];
|
|
502
828
|
this.inFlight = /* @__PURE__ */ new Set();
|
|
503
|
-
|
|
829
|
+
this.hasShutdown = false;
|
|
830
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
504
831
|
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
505
832
|
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
506
833
|
this.enabled = opts.enabled !== false;
|
|
@@ -513,13 +840,79 @@ var TraceShipper = class {
|
|
|
513
840
|
this.prefix = `[raindrop-ai/${this.sdkName}]`;
|
|
514
841
|
this.serviceName = (_g = opts.serviceName) != null ? _g : "raindrop.core";
|
|
515
842
|
this.serviceVersion = (_h = opts.serviceVersion) != null ? _h : "0.0.0";
|
|
516
|
-
|
|
517
|
-
if (
|
|
518
|
-
this.
|
|
519
|
-
|
|
520
|
-
|
|
843
|
+
this.localDebuggerUrl = (_i = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _i : void 0;
|
|
844
|
+
if (this.debug && this.localDebuggerUrl) {
|
|
845
|
+
console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
|
|
846
|
+
}
|
|
847
|
+
this.transformSpanHook = opts.transformSpan;
|
|
848
|
+
this.disableDefaultRedaction = opts.disableDefaultRedaction === true;
|
|
849
|
+
this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Cap every string attribute value on the span. O(#attributes) length
|
|
853
|
+
* checks; only oversized values pay a slice. Runs AFTER the redaction
|
|
854
|
+
* pipeline so the default secret-scrub still sees parseable JSON in
|
|
855
|
+
* `ai.request.providerOptions` / `ai.response.providerMetadata` (capping
|
|
856
|
+
* first could cut a JSON blob mid-way, fail the parse, and ship secrets
|
|
857
|
+
* in the surviving prefix).
|
|
858
|
+
*
|
|
859
|
+
* A stricter `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var is honored
|
|
860
|
+
* for span content, matching the Python SDK and the OTel SDK convention.
|
|
861
|
+
*/
|
|
862
|
+
capSpanAttributes(span) {
|
|
863
|
+
var _a;
|
|
864
|
+
const maxChars = applyOtelSpanAttributeLimit(
|
|
865
|
+
resolveMaxTextFieldChars(this.maxTextFieldCharsOpt)
|
|
866
|
+
);
|
|
867
|
+
const attrs = span.attributes;
|
|
868
|
+
if (!attrs || attrs.length === 0) return span;
|
|
869
|
+
let nextAttrs;
|
|
870
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
871
|
+
const attr = attrs[i];
|
|
872
|
+
const value = (_a = attr.value) == null ? void 0 : _a.stringValue;
|
|
873
|
+
if (typeof value !== "string" || value.length <= maxChars) continue;
|
|
874
|
+
if (!nextAttrs) nextAttrs = attrs.slice();
|
|
875
|
+
nextAttrs[i] = {
|
|
876
|
+
key: attr.key,
|
|
877
|
+
value: { ...attr.value, stringValue: capText(value, maxChars) }
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
if (!nextAttrs) return span;
|
|
881
|
+
return { ...span, attributes: nextAttrs };
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Apply the user `transformSpan` hook (if any) followed by the default
|
|
885
|
+
* redactor (unless disabled). Returns either the (possibly new) span to
|
|
886
|
+
* ship, or `null` to drop the span entirely.
|
|
887
|
+
*
|
|
888
|
+
* Ordering: user hook runs first so callers can rewrite the span freely
|
|
889
|
+
* (rename attrs, add new ones, scrub things the default doesn't know
|
|
890
|
+
* about). The default redactor then runs on whatever the user produced,
|
|
891
|
+
* acting as the always-on floor for documented BYOK secrets. If the user
|
|
892
|
+
* sets `disableDefaultRedaction: true`, the floor is skipped.
|
|
893
|
+
*
|
|
894
|
+
* Fail-closed: if the user hook throws, the span is dropped — a buggy
|
|
895
|
+
* hook can never accidentally ship raw, un-redacted spans.
|
|
896
|
+
*/
|
|
897
|
+
redactSpan(span) {
|
|
898
|
+
let current = span;
|
|
899
|
+
if (this.transformSpanHook) {
|
|
900
|
+
try {
|
|
901
|
+
const result = this.transformSpanHook(current);
|
|
902
|
+
if (result === null) return null;
|
|
903
|
+
if (result !== void 0) current = result;
|
|
904
|
+
} catch (err) {
|
|
905
|
+
if (this.debug) {
|
|
906
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
907
|
+
console.warn(`${this.prefix} transformSpan hook threw: ${msg}`);
|
|
908
|
+
}
|
|
909
|
+
return null;
|
|
521
910
|
}
|
|
522
911
|
}
|
|
912
|
+
if (!this.disableDefaultRedaction) {
|
|
913
|
+
current = defaultTransformSpan(current);
|
|
914
|
+
}
|
|
915
|
+
return this.capSpanAttributes(current);
|
|
523
916
|
}
|
|
524
917
|
isDebugEnabled() {
|
|
525
918
|
return this.debug;
|
|
@@ -537,8 +930,8 @@ var TraceShipper = class {
|
|
|
537
930
|
];
|
|
538
931
|
if ((_b = args.attributes) == null ? void 0 : _b.length) attrs.push(...args.attributes);
|
|
539
932
|
const span = { ids, name: args.name, startTimeUnixNano: started, attributes: attrs };
|
|
540
|
-
|
|
541
|
-
|
|
933
|
+
this.mirrorToLocalDebugger(
|
|
934
|
+
buildOtlpSpan({
|
|
542
935
|
ids: span.ids,
|
|
543
936
|
name: span.name,
|
|
544
937
|
startTimeUnixNano: span.startTimeUnixNano,
|
|
@@ -546,16 +939,21 @@ var TraceShipper = class {
|
|
|
546
939
|
// placeholder — will be updated on endSpan
|
|
547
940
|
attributes: span.attributes,
|
|
548
941
|
status: { code: SpanStatusCode.UNSET }
|
|
549
|
-
})
|
|
550
|
-
|
|
551
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
552
|
-
baseUrl: this.localDebuggerUrl,
|
|
553
|
-
debug: false,
|
|
554
|
-
sdkName: this.sdkName
|
|
555
|
-
});
|
|
556
|
-
}
|
|
942
|
+
})
|
|
943
|
+
);
|
|
557
944
|
return span;
|
|
558
945
|
}
|
|
946
|
+
mirrorToLocalDebugger(span) {
|
|
947
|
+
if (!this.localDebuggerUrl) return;
|
|
948
|
+
const redacted = this.redactSpan(span);
|
|
949
|
+
if (redacted === null) return;
|
|
950
|
+
const body = buildExportTraceServiceRequest([redacted], this.serviceName, this.serviceVersion);
|
|
951
|
+
mirrorTraceExportToLocalDebugger(body, {
|
|
952
|
+
baseUrl: this.localDebuggerUrl,
|
|
953
|
+
debug: false,
|
|
954
|
+
sdkName: this.sdkName
|
|
955
|
+
});
|
|
956
|
+
}
|
|
559
957
|
endSpan(span, extra) {
|
|
560
958
|
var _a, _b;
|
|
561
959
|
if (span.endTimeUnixNano) return;
|
|
@@ -577,14 +975,7 @@ var TraceShipper = class {
|
|
|
577
975
|
status
|
|
578
976
|
});
|
|
579
977
|
this.enqueue(otlp);
|
|
580
|
-
|
|
581
|
-
const body = buildExportTraceServiceRequest([otlp], this.serviceName, this.serviceVersion);
|
|
582
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
583
|
-
baseUrl: this.localDebuggerUrl,
|
|
584
|
-
debug: false,
|
|
585
|
-
sdkName: this.sdkName
|
|
586
|
-
});
|
|
587
|
-
}
|
|
978
|
+
this.mirrorToLocalDebugger(otlp);
|
|
588
979
|
}
|
|
589
980
|
createSpan(args) {
|
|
590
981
|
var _a;
|
|
@@ -602,14 +993,7 @@ var TraceShipper = class {
|
|
|
602
993
|
status: args.status
|
|
603
994
|
});
|
|
604
995
|
this.enqueue(otlp);
|
|
605
|
-
|
|
606
|
-
const body = buildExportTraceServiceRequest([otlp], this.serviceName, this.serviceVersion);
|
|
607
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
608
|
-
baseUrl: this.localDebuggerUrl,
|
|
609
|
-
debug: false,
|
|
610
|
-
sdkName: this.sdkName
|
|
611
|
-
});
|
|
612
|
-
}
|
|
996
|
+
this.mirrorToLocalDebugger(otlp);
|
|
613
997
|
}
|
|
614
998
|
enqueue(span) {
|
|
615
999
|
if (!this.enabled) return;
|
|
@@ -621,10 +1005,12 @@ var TraceShipper = class {
|
|
|
621
1005
|
)}`
|
|
622
1006
|
);
|
|
623
1007
|
}
|
|
1008
|
+
const redacted = this.redactSpan(span);
|
|
1009
|
+
if (redacted === null) return;
|
|
624
1010
|
if (this.queue.length >= this.maxQueueSize) {
|
|
625
1011
|
this.queue.shift();
|
|
626
1012
|
}
|
|
627
|
-
this.queue.push(
|
|
1013
|
+
this.queue.push(redacted);
|
|
628
1014
|
if (this.queue.length >= this.maxBatchSize) {
|
|
629
1015
|
void this.flush().catch(() => {
|
|
630
1016
|
});
|
|
@@ -646,6 +1032,17 @@ var TraceShipper = class {
|
|
|
646
1032
|
}
|
|
647
1033
|
while (this.queue.length > 0) {
|
|
648
1034
|
const batch = this.queue.splice(0, this.maxBatchSize);
|
|
1035
|
+
if (!this.writeKey) continue;
|
|
1036
|
+
const opts = this.requestOpts();
|
|
1037
|
+
if (!opts) {
|
|
1038
|
+
rateLimitedLog(
|
|
1039
|
+
`${this.prefix}.shutdown_deadline`,
|
|
1040
|
+
() => console.warn(
|
|
1041
|
+
`${this.prefix} shutdown flush deadline exceeded; dropping ${batch.length} spans`
|
|
1042
|
+
)
|
|
1043
|
+
);
|
|
1044
|
+
continue;
|
|
1045
|
+
}
|
|
649
1046
|
const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
|
|
650
1047
|
const url = `${this.baseUrl}traces`;
|
|
651
1048
|
if (this.debug) {
|
|
@@ -654,11 +1051,7 @@ var TraceShipper = class {
|
|
|
654
1051
|
endpoint: url
|
|
655
1052
|
});
|
|
656
1053
|
}
|
|
657
|
-
const p = postJson(url, body, this.authHeaders(),
|
|
658
|
-
maxAttempts: 3,
|
|
659
|
-
debug: this.debug,
|
|
660
|
-
sdkName: this.sdkName
|
|
661
|
-
});
|
|
1054
|
+
const p = postJson(url, body, this.authHeaders(), opts);
|
|
662
1055
|
this.inFlight.add(p);
|
|
663
1056
|
try {
|
|
664
1057
|
try {
|
|
@@ -666,21 +1059,61 @@ var TraceShipper = class {
|
|
|
666
1059
|
if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
|
|
667
1060
|
} catch (err) {
|
|
668
1061
|
const msg = err instanceof Error ? err.message : String(err);
|
|
669
|
-
|
|
1062
|
+
rateLimitedLog(
|
|
1063
|
+
`${this.prefix}.send_spans_failed`,
|
|
1064
|
+
() => console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`)
|
|
1065
|
+
);
|
|
670
1066
|
}
|
|
671
1067
|
} finally {
|
|
672
1068
|
this.inFlight.delete(p);
|
|
673
1069
|
}
|
|
674
1070
|
}
|
|
675
1071
|
}
|
|
1072
|
+
/** See EventShipper.requestOpts — same shutdown-budget semantics. */
|
|
1073
|
+
requestOpts() {
|
|
1074
|
+
if (this.shutdownDeadlineAt !== void 0) {
|
|
1075
|
+
const remainingMs = this.shutdownDeadlineAt - Date.now();
|
|
1076
|
+
if (remainingMs <= 0) return null;
|
|
1077
|
+
return {
|
|
1078
|
+
maxAttempts: 1,
|
|
1079
|
+
debug: this.debug,
|
|
1080
|
+
sdkName: this.sdkName,
|
|
1081
|
+
timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
if (this.hasShutdown) {
|
|
1085
|
+
return {
|
|
1086
|
+
maxAttempts: 1,
|
|
1087
|
+
debug: this.debug,
|
|
1088
|
+
sdkName: this.sdkName,
|
|
1089
|
+
timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
|
|
1093
|
+
}
|
|
676
1094
|
async shutdown() {
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
1095
|
+
this.hasShutdown = true;
|
|
1096
|
+
this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
|
|
1097
|
+
try {
|
|
1098
|
+
if (this.timer) {
|
|
1099
|
+
clearTimeout(this.timer);
|
|
1100
|
+
this.timer = void 0;
|
|
1101
|
+
}
|
|
1102
|
+
const drain = async () => {
|
|
1103
|
+
await this.flush();
|
|
1104
|
+
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
1105
|
+
})));
|
|
1106
|
+
};
|
|
1107
|
+
const settled = await raceWithTimeout(drain(), SHUTDOWN_DEADLINE_MS);
|
|
1108
|
+
if (!settled) {
|
|
1109
|
+
rateLimitedLog(
|
|
1110
|
+
`${this.prefix}.shutdown_deadline`,
|
|
1111
|
+
() => console.warn(`${this.prefix} shutdown flush deadline exceeded; abandoning in-flight spans`)
|
|
1112
|
+
);
|
|
1113
|
+
}
|
|
1114
|
+
} finally {
|
|
1115
|
+
this.shutdownDeadlineAt = void 0;
|
|
680
1116
|
}
|
|
681
|
-
await this.flush();
|
|
682
|
-
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
683
|
-
})));
|
|
684
1117
|
}
|
|
685
1118
|
};
|
|
686
1119
|
|
|
@@ -1029,18 +1462,85 @@ function transcriptToProperties(summary) {
|
|
|
1029
1462
|
|
|
1030
1463
|
// src/event-mapper.ts
|
|
1031
1464
|
var MAX_ATTR_LENGTH = 32768;
|
|
1465
|
+
var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
|
|
1466
|
+
var MAX_TEXT_FIELD_CHARS = 1e6;
|
|
1467
|
+
var MAX_BOUNDED_DEPTH = 12;
|
|
1032
1468
|
function truncate(value) {
|
|
1033
1469
|
if (value === void 0) return void 0;
|
|
1034
1470
|
if (value.length <= MAX_ATTR_LENGTH) return value;
|
|
1035
1471
|
const suffix = "\n...[truncated]";
|
|
1036
1472
|
return value.slice(0, MAX_ATTR_LENGTH - suffix.length) + suffix;
|
|
1037
1473
|
}
|
|
1474
|
+
function capText2(value, limit = MAX_TEXT_FIELD_CHARS) {
|
|
1475
|
+
if (value.length <= limit) return value;
|
|
1476
|
+
if (limit > TRUNCATION_MARKER2.length) {
|
|
1477
|
+
return value.slice(0, limit - TRUNCATION_MARKER2.length) + TRUNCATION_MARKER2;
|
|
1478
|
+
}
|
|
1479
|
+
return value.slice(0, limit);
|
|
1480
|
+
}
|
|
1481
|
+
function boundedClone2(value, budget, depth) {
|
|
1482
|
+
if (budget.remaining <= 0) return TRUNCATION_MARKER2;
|
|
1483
|
+
if (typeof value === "string") {
|
|
1484
|
+
if (value.length > budget.remaining) {
|
|
1485
|
+
const taken = value.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
|
|
1486
|
+
budget.remaining = 0;
|
|
1487
|
+
return taken;
|
|
1488
|
+
}
|
|
1489
|
+
budget.remaining -= Math.max(value.length, 1);
|
|
1490
|
+
return value;
|
|
1491
|
+
}
|
|
1492
|
+
if (value === null || typeof value === "number" || typeof value === "boolean") {
|
|
1493
|
+
budget.remaining -= 8;
|
|
1494
|
+
return value;
|
|
1495
|
+
}
|
|
1496
|
+
if (typeof value !== "object") {
|
|
1497
|
+
budget.remaining -= 8;
|
|
1498
|
+
return value;
|
|
1499
|
+
}
|
|
1500
|
+
if (depth >= MAX_BOUNDED_DEPTH) {
|
|
1501
|
+
budget.remaining -= 16;
|
|
1502
|
+
return `<max depth: ${TRUNCATION_MARKER2}>`;
|
|
1503
|
+
}
|
|
1504
|
+
const withToJson = value;
|
|
1505
|
+
if (typeof withToJson.toJSON === "function") {
|
|
1506
|
+
try {
|
|
1507
|
+
return boundedClone2(withToJson.toJSON(), budget, depth + 1);
|
|
1508
|
+
} catch (e) {
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
if (Array.isArray(value)) {
|
|
1512
|
+
const out2 = [];
|
|
1513
|
+
for (const item of value) {
|
|
1514
|
+
if (budget.remaining <= 0) {
|
|
1515
|
+
out2.push(TRUNCATION_MARKER2);
|
|
1516
|
+
break;
|
|
1517
|
+
}
|
|
1518
|
+
out2.push(boundedClone2(item, budget, depth + 1));
|
|
1519
|
+
}
|
|
1520
|
+
return out2;
|
|
1521
|
+
}
|
|
1522
|
+
const out = {};
|
|
1523
|
+
for (const key of Object.keys(value)) {
|
|
1524
|
+
if (budget.remaining <= 0) {
|
|
1525
|
+
out["..."] = TRUNCATION_MARKER2;
|
|
1526
|
+
break;
|
|
1527
|
+
}
|
|
1528
|
+
budget.remaining -= Math.max(key.length, 1);
|
|
1529
|
+
out[key] = boundedClone2(value[key], budget, depth + 1);
|
|
1530
|
+
}
|
|
1531
|
+
return out;
|
|
1532
|
+
}
|
|
1038
1533
|
function safeStringify(value) {
|
|
1039
1534
|
if (value === void 0 || value === null) return void 0;
|
|
1040
1535
|
try {
|
|
1041
|
-
|
|
1536
|
+
const pruned = boundedClone2(
|
|
1537
|
+
value,
|
|
1538
|
+
{ remaining: MAX_ATTR_LENGTH + TRUNCATION_MARKER2.length + 256 },
|
|
1539
|
+
0
|
|
1540
|
+
);
|
|
1541
|
+
return truncate(JSON.stringify(pruned));
|
|
1042
1542
|
} catch (e) {
|
|
1043
|
-
return truncate(String(value));
|
|
1543
|
+
return truncate(capText2(String(value), MAX_ATTR_LENGTH + 16));
|
|
1044
1544
|
}
|
|
1045
1545
|
}
|
|
1046
1546
|
var STATE_DIR = (0, import_node_path2.join)((0, import_node_os2.tmpdir)(), "raindrop-claude-code");
|
|
@@ -1221,7 +1721,9 @@ async function handleUserPromptSubmit(payload, convoId, config, properties, even
|
|
|
1221
1721
|
userId: config.userId,
|
|
1222
1722
|
convoId,
|
|
1223
1723
|
eventName: config.eventName,
|
|
1224
|
-
|
|
1724
|
+
// Capped so a pasted multi-MB prompt can't produce an event that the
|
|
1725
|
+
// ingest API rejects (silent data loss) — it lands truncated instead.
|
|
1726
|
+
input: payload.prompt !== void 0 ? capText2(payload.prompt) : void 0,
|
|
1225
1727
|
model,
|
|
1226
1728
|
properties
|
|
1227
1729
|
};
|
|
@@ -1344,7 +1846,7 @@ async function handlePostCompact(payload, eventId, convoId, config, properties,
|
|
|
1344
1846
|
properties: {
|
|
1345
1847
|
...properties,
|
|
1346
1848
|
compaction_trigger: payload.trigger,
|
|
1347
|
-
compact_summary: payload.compact_summary
|
|
1849
|
+
compact_summary: payload.compact_summary !== void 0 ? capText2(payload.compact_summary) : void 0
|
|
1348
1850
|
}
|
|
1349
1851
|
});
|
|
1350
1852
|
}
|
|
@@ -1579,7 +2081,7 @@ function enrichFromTranscript(payload, eventId, traceShipper) {
|
|
|
1579
2081
|
attrInt("gen_ai.usage.prompt_tokens", phase.inputTokens),
|
|
1580
2082
|
attrInt("gen_ai.usage.completion_tokens", phase.outputTokens),
|
|
1581
2083
|
...phase.cacheReadTokens > 0 ? [attrInt("gen_ai.usage.cache_read_tokens", phase.cacheReadTokens)] : [],
|
|
1582
|
-
attrString("ai.response.text", outputText),
|
|
2084
|
+
attrString("ai.response.text", truncate(outputText)),
|
|
1583
2085
|
...toolCallNames ? [attrString("ai.toolCall.names", toolCallNames)] : [],
|
|
1584
2086
|
...phase.hasThinking ? [attrString("ai.has_thinking", "true")] : [],
|
|
1585
2087
|
attrInt("ai.llm_call.index", emittedPhaseIndex)
|
|
@@ -1639,7 +2141,7 @@ async function handleStopOrFailure(payload, eventId, convoId, config, properties
|
|
|
1639
2141
|
userId: config.userId,
|
|
1640
2142
|
convoId,
|
|
1641
2143
|
eventName: config.eventName,
|
|
1642
|
-
output,
|
|
2144
|
+
output: output !== void 0 ? capText2(output) : void 0,
|
|
1643
2145
|
...(summary == null ? void 0 : summary.model) ? { model: summary.model } : {},
|
|
1644
2146
|
properties: {
|
|
1645
2147
|
...properties,
|
|
@@ -1670,6 +2172,7 @@ var import_node_path3 = require("path");
|
|
|
1670
2172
|
var DEFAULT_PORT = 5899;
|
|
1671
2173
|
var DEFAULT_URL = `http://localhost:${DEFAULT_PORT}/v1/`;
|
|
1672
2174
|
var HEALTH_TIMEOUT_MS = 300;
|
|
2175
|
+
var MIRROR_TIMEOUT_MS = 2e3;
|
|
1673
2176
|
var CACHE_DIR = (0, import_node_path3.join)((0, import_node_os3.tmpdir)(), "raindrop-claude-code");
|
|
1674
2177
|
var CACHE_FILE = (0, import_node_path3.join)(CACHE_DIR, "debugger_cache");
|
|
1675
2178
|
var CACHE_TTL_MS = 5e3;
|
|
@@ -1693,10 +2196,8 @@ function writeCache(url) {
|
|
|
1693
2196
|
async function healthPing(baseUrl) {
|
|
1694
2197
|
const healthUrl = baseUrl.replace(/\/v1\/$/, "/health");
|
|
1695
2198
|
try {
|
|
1696
|
-
const
|
|
1697
|
-
const
|
|
1698
|
-
const resp = await fetch(healthUrl, { signal: controller.signal });
|
|
1699
|
-
clearTimeout(timeout);
|
|
2199
|
+
const signal = AbortSignal.timeout(HEALTH_TIMEOUT_MS);
|
|
2200
|
+
const resp = await fetch(healthUrl, { signal });
|
|
1700
2201
|
if (resp.ok) {
|
|
1701
2202
|
const body = await resp.json();
|
|
1702
2203
|
if (body.ok) return baseUrl;
|
|
@@ -1739,7 +2240,8 @@ function mirrorEventToLocalDebugger(baseUrl, payload, debug) {
|
|
|
1739
2240
|
void fetch(url, {
|
|
1740
2241
|
method: "POST",
|
|
1741
2242
|
headers: { "Content-Type": "application/json" },
|
|
1742
|
-
body: JSON.stringify(payload)
|
|
2243
|
+
body: JSON.stringify(payload),
|
|
2244
|
+
signal: AbortSignal.timeout(MIRROR_TIMEOUT_MS)
|
|
1743
2245
|
}).catch(() => {
|
|
1744
2246
|
});
|
|
1745
2247
|
}
|