@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.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// ../core/dist/chunk-
|
|
1
|
+
// ../core/dist/chunk-U5HUTMR5.js
|
|
2
2
|
function getCrypto() {
|
|
3
3
|
const c = globalThis.crypto;
|
|
4
4
|
return c;
|
|
@@ -42,6 +42,8 @@ function base64Encode(bytes) {
|
|
|
42
42
|
}
|
|
43
43
|
return out;
|
|
44
44
|
}
|
|
45
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
46
|
+
var MAX_RETRY_DELAY_MS = 3e4;
|
|
45
47
|
function wait(ms) {
|
|
46
48
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
47
49
|
}
|
|
@@ -49,6 +51,46 @@ function formatEndpoint(endpoint) {
|
|
|
49
51
|
if (!endpoint) return void 0;
|
|
50
52
|
return endpoint.endsWith("/") ? endpoint : `${endpoint}/`;
|
|
51
53
|
}
|
|
54
|
+
function redactUrlForLog(url) {
|
|
55
|
+
try {
|
|
56
|
+
const parsed = new URL(url);
|
|
57
|
+
parsed.username = "";
|
|
58
|
+
parsed.password = "";
|
|
59
|
+
parsed.search = "";
|
|
60
|
+
parsed.hash = "";
|
|
61
|
+
return parsed.toString();
|
|
62
|
+
} catch (e) {
|
|
63
|
+
return "<unparseable-url>";
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
var RATE_LIMITED_LOG_INTERVAL_MS = 3e4;
|
|
67
|
+
var rateLimitedLogLast = /* @__PURE__ */ new Map();
|
|
68
|
+
function rateLimitedLog(key, log) {
|
|
69
|
+
const now = Date.now();
|
|
70
|
+
const last = rateLimitedLogLast.get(key);
|
|
71
|
+
if (last !== void 0 && now - last < RATE_LIMITED_LOG_INTERVAL_MS) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
rateLimitedLogLast.set(key, now);
|
|
75
|
+
log();
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
async function raceWithTimeout(promise, timeoutMs) {
|
|
79
|
+
let timer;
|
|
80
|
+
const settledInTime = await Promise.race([
|
|
81
|
+
promise.then(
|
|
82
|
+
() => true,
|
|
83
|
+
() => true
|
|
84
|
+
),
|
|
85
|
+
new Promise((resolve) => {
|
|
86
|
+
var _a;
|
|
87
|
+
timer = setTimeout(() => resolve(false), Math.max(0, timeoutMs));
|
|
88
|
+
(_a = timer.unref) == null ? void 0 : _a.call(timer);
|
|
89
|
+
})
|
|
90
|
+
]);
|
|
91
|
+
if (timer) clearTimeout(timer);
|
|
92
|
+
return settledInTime;
|
|
93
|
+
}
|
|
52
94
|
function parseRetryAfter(headers) {
|
|
53
95
|
var _a;
|
|
54
96
|
const value = (_a = headers.get("Retry-After")) != null ? _a : headers.get("retry-after");
|
|
@@ -65,12 +107,12 @@ function parseRetryAfter(headers) {
|
|
|
65
107
|
function getRetryDelayMs(attemptNumber, previousError) {
|
|
66
108
|
if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
|
|
67
109
|
const v = previousError.retryAfterMs;
|
|
68
|
-
if (typeof v === "number") return Math.max(0, v);
|
|
110
|
+
if (typeof v === "number") return Math.min(Math.max(0, v), MAX_RETRY_DELAY_MS);
|
|
69
111
|
}
|
|
70
112
|
if (attemptNumber <= 1) return 0;
|
|
71
113
|
const base = 500;
|
|
72
114
|
const factor = Math.pow(2, attemptNumber - 2);
|
|
73
|
-
return base * factor;
|
|
115
|
+
return Math.min(base * factor, MAX_RETRY_DELAY_MS);
|
|
74
116
|
}
|
|
75
117
|
async function withRetry(operation, opName, opts) {
|
|
76
118
|
const prefix = opts.sdkName ? `[raindrop-ai/${opts.sdkName}]` : "[raindrop-ai/core]";
|
|
@@ -105,7 +147,9 @@ async function withRetry(operation, opName, opts) {
|
|
|
105
147
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
106
148
|
}
|
|
107
149
|
async function postJson(url, body, headers, opts) {
|
|
108
|
-
|
|
150
|
+
var _a;
|
|
151
|
+
const opName = `POST ${redactUrlForLog(url)}`;
|
|
152
|
+
const timeoutMs = (_a = opts.timeoutMs) != null ? _a : DEFAULT_REQUEST_TIMEOUT_MS;
|
|
109
153
|
await withRetry(
|
|
110
154
|
async () => {
|
|
111
155
|
const resp = await fetch(url, {
|
|
@@ -114,7 +158,8 @@ async function postJson(url, body, headers, opts) {
|
|
|
114
158
|
"Content-Type": "application/json",
|
|
115
159
|
...headers
|
|
116
160
|
},
|
|
117
|
-
body: JSON.stringify(body)
|
|
161
|
+
body: JSON.stringify(body),
|
|
162
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
118
163
|
});
|
|
119
164
|
if (!resp.ok) {
|
|
120
165
|
const text = await resp.text().catch(() => "");
|
|
@@ -131,6 +176,27 @@ async function postJson(url, body, headers, opts) {
|
|
|
131
176
|
opts
|
|
132
177
|
);
|
|
133
178
|
}
|
|
179
|
+
var DEFAULT_MAX_TEXT_FIELD_CHARS = 1e6;
|
|
180
|
+
var TRUNCATION_MARKER = "...[truncated by raindrop]";
|
|
181
|
+
var currentDefaultMaxTextFieldChars = DEFAULT_MAX_TEXT_FIELD_CHARS;
|
|
182
|
+
function resolveMaxTextFieldChars(value) {
|
|
183
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
184
|
+
return Math.floor(value);
|
|
185
|
+
}
|
|
186
|
+
return currentDefaultMaxTextFieldChars;
|
|
187
|
+
}
|
|
188
|
+
function truncateToLimit(text, limit) {
|
|
189
|
+
if (limit > TRUNCATION_MARKER.length) {
|
|
190
|
+
return text.slice(0, limit - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;
|
|
191
|
+
}
|
|
192
|
+
return text.slice(0, Math.max(0, limit));
|
|
193
|
+
}
|
|
194
|
+
function capText(value, limit) {
|
|
195
|
+
if (typeof value !== "string") return value;
|
|
196
|
+
const max = limit != null ? limit : currentDefaultMaxTextFieldChars;
|
|
197
|
+
if (value.length <= max) return value;
|
|
198
|
+
return truncateToLimit(value, max);
|
|
199
|
+
}
|
|
134
200
|
var SpanStatusCode = {
|
|
135
201
|
UNSET: 0,
|
|
136
202
|
OK: 1,
|
|
@@ -188,6 +254,93 @@ function buildExportTraceServiceRequest(spans, serviceName = "raindrop.core", se
|
|
|
188
254
|
]
|
|
189
255
|
};
|
|
190
256
|
}
|
|
257
|
+
var LOCAL_DEBUGGER_ENV_VAR = "RAINDROP_LOCAL_DEBUGGER";
|
|
258
|
+
var WORKSHOP_ENV_VAR = "RAINDROP_WORKSHOP";
|
|
259
|
+
var DEFAULT_LOCAL_WORKSHOP_URL = "http://localhost:5899/v1/";
|
|
260
|
+
function readEnvVar(name) {
|
|
261
|
+
var _a;
|
|
262
|
+
try {
|
|
263
|
+
const env = (_a = globalThis == null ? void 0 : globalThis.process) == null ? void 0 : _a.env;
|
|
264
|
+
if (env && typeof env[name] === "string" && env[name].length > 0) {
|
|
265
|
+
return env[name];
|
|
266
|
+
}
|
|
267
|
+
} catch (e) {
|
|
268
|
+
}
|
|
269
|
+
return void 0;
|
|
270
|
+
}
|
|
271
|
+
function readWorkshopEnv() {
|
|
272
|
+
const raw = readEnvVar(WORKSHOP_ENV_VAR);
|
|
273
|
+
if (raw === void 0) return void 0;
|
|
274
|
+
const trimmed = raw.trim();
|
|
275
|
+
if (trimmed.length === 0) return void 0;
|
|
276
|
+
if (/^https?:\/\//i.test(trimmed)) return { url: trimmed };
|
|
277
|
+
if (/^(1|true|yes|on)$/i.test(trimmed)) return "enable";
|
|
278
|
+
if (/^(0|false|no|off)$/i.test(trimmed)) return "disable";
|
|
279
|
+
return void 0;
|
|
280
|
+
}
|
|
281
|
+
function isLocalDevHost(hostname) {
|
|
282
|
+
if (!hostname) return false;
|
|
283
|
+
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "0.0.0.0" || hostname === "::1") {
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
if (hostname.endsWith(".localhost")) return true;
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
function readRuntimeHostname() {
|
|
290
|
+
try {
|
|
291
|
+
const loc = globalThis == null ? void 0 : globalThis.location;
|
|
292
|
+
if (loc && typeof loc.hostname === "string" && loc.hostname.length > 0) {
|
|
293
|
+
return loc.hostname;
|
|
294
|
+
}
|
|
295
|
+
} catch (e) {
|
|
296
|
+
}
|
|
297
|
+
return void 0;
|
|
298
|
+
}
|
|
299
|
+
function shouldAutoEnableLocalWorkshop() {
|
|
300
|
+
if (isLocalDevHost(readRuntimeHostname())) return true;
|
|
301
|
+
if (readEnvVar("NODE_ENV") === "development") return true;
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
function resolveLocalDebuggerBaseUrl(baseUrl) {
|
|
305
|
+
var _a, _b, _c;
|
|
306
|
+
if (baseUrl === null) return null;
|
|
307
|
+
if (typeof baseUrl === "string" && baseUrl.length > 0) {
|
|
308
|
+
return (_a = formatEndpoint(baseUrl)) != null ? _a : null;
|
|
309
|
+
}
|
|
310
|
+
const explicitUrlEnv = readEnvVar(LOCAL_DEBUGGER_ENV_VAR);
|
|
311
|
+
if (explicitUrlEnv) return (_b = formatEndpoint(explicitUrlEnv)) != null ? _b : null;
|
|
312
|
+
const workshopEnv = readWorkshopEnv();
|
|
313
|
+
if (workshopEnv === "disable") return null;
|
|
314
|
+
if (workshopEnv === "enable") return DEFAULT_LOCAL_WORKSHOP_URL;
|
|
315
|
+
if (workshopEnv && "url" in workshopEnv) return (_c = formatEndpoint(workshopEnv.url)) != null ? _c : null;
|
|
316
|
+
if (shouldAutoEnableLocalWorkshop()) return DEFAULT_LOCAL_WORKSHOP_URL;
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
function mirrorTraceExportToLocalDebugger(body, options = {}) {
|
|
320
|
+
var _a;
|
|
321
|
+
const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
|
|
322
|
+
if (!baseUrl) return;
|
|
323
|
+
void postJson(`${baseUrl}traces`, body, {}, {
|
|
324
|
+
maxAttempts: 1,
|
|
325
|
+
debug: (_a = options.debug) != null ? _a : false,
|
|
326
|
+
sdkName: options.sdkName
|
|
327
|
+
}).catch(() => {
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
function mirrorPartialEventToLocalDebugger(event, options = {}) {
|
|
331
|
+
var _a;
|
|
332
|
+
const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
|
|
333
|
+
if (!baseUrl) return;
|
|
334
|
+
const headers = options.writeKey ? { Authorization: `Bearer ${options.writeKey}` } : {};
|
|
335
|
+
void postJson(`${baseUrl}events/track_partial`, event, headers, {
|
|
336
|
+
maxAttempts: 1,
|
|
337
|
+
debug: (_a = options.debug) != null ? _a : false,
|
|
338
|
+
sdkName: options.sdkName
|
|
339
|
+
}).catch(() => {
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
var SHUTDOWN_DEADLINE_MS = 1e4;
|
|
343
|
+
var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
191
344
|
function mergePatches(target, source) {
|
|
192
345
|
var _a, _b, _c, _d;
|
|
193
346
|
const out = { ...target, ...source };
|
|
@@ -205,7 +358,8 @@ var EventShipper = class {
|
|
|
205
358
|
this.sticky = /* @__PURE__ */ new Map();
|
|
206
359
|
this.timers = /* @__PURE__ */ new Map();
|
|
207
360
|
this.inFlight = /* @__PURE__ */ new Set();
|
|
208
|
-
|
|
361
|
+
this.hasShutdown = false;
|
|
362
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
209
363
|
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
210
364
|
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
211
365
|
this.enabled = opts.enabled !== false;
|
|
@@ -214,11 +368,16 @@ var EventShipper = class {
|
|
|
214
368
|
this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
|
|
215
369
|
this.prefix = `[raindrop-ai/${this.sdkName}]`;
|
|
216
370
|
this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
|
|
371
|
+
this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
|
|
372
|
+
this.localDebuggerUrl = (_f = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _f : void 0;
|
|
373
|
+
if (this.debug && this.localDebuggerUrl) {
|
|
374
|
+
console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
|
|
375
|
+
}
|
|
217
376
|
const isNode = typeof process !== "undefined" && typeof process.version === "string";
|
|
218
377
|
this.context = {
|
|
219
378
|
library: {
|
|
220
|
-
name: (
|
|
221
|
-
version: (
|
|
379
|
+
name: (_g = opts.libraryName) != null ? _g : "@raindrop-ai/core",
|
|
380
|
+
version: (_h = opts.libraryVersion) != null ? _h : "0.0.0"
|
|
222
381
|
},
|
|
223
382
|
metadata: {
|
|
224
383
|
jsRuntime: isNode ? "node" : "web",
|
|
@@ -232,10 +391,52 @@ var EventShipper = class {
|
|
|
232
391
|
authHeaders() {
|
|
233
392
|
return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
|
|
234
393
|
}
|
|
394
|
+
/**
|
|
395
|
+
* Build the retry/timeout options for one POST, honoring the shutdown
|
|
396
|
+
* deadline. Returns `null` when the shutdown drain window is exhausted —
|
|
397
|
+
* the caller must drop the payload (with a rate-limited warning) instead
|
|
398
|
+
* of issuing a request that could outlive process exit.
|
|
399
|
+
*
|
|
400
|
+
* Checked fresh on EVERY send, so a shutdown that begins while the flush
|
|
401
|
+
* path is mid-drain takes effect immediately: no further retries, and the
|
|
402
|
+
* per-attempt timeout is clamped to the remaining window. After
|
|
403
|
+
* `shutdown()` returns (deadline cleared, `hasShutdown` still set),
|
|
404
|
+
* sends — late callers, or flush work the deadline abandoned mid-drain —
|
|
405
|
+
* run as a single short attempt rather than regaining the full retry
|
|
406
|
+
* schedule.
|
|
407
|
+
*/
|
|
408
|
+
requestOpts() {
|
|
409
|
+
if (this.shutdownDeadlineAt !== void 0) {
|
|
410
|
+
const remainingMs = this.shutdownDeadlineAt - Date.now();
|
|
411
|
+
if (remainingMs <= 0) return null;
|
|
412
|
+
return {
|
|
413
|
+
maxAttempts: 1,
|
|
414
|
+
debug: this.debug,
|
|
415
|
+
sdkName: this.sdkName,
|
|
416
|
+
timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
if (this.hasShutdown) {
|
|
420
|
+
return {
|
|
421
|
+
maxAttempts: 1,
|
|
422
|
+
debug: this.debug,
|
|
423
|
+
sdkName: this.sdkName,
|
|
424
|
+
timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
|
|
428
|
+
}
|
|
235
429
|
async patch(eventId, patch) {
|
|
236
430
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
237
431
|
if (!this.enabled) return;
|
|
238
432
|
if (!eventId || !eventId.trim()) return;
|
|
433
|
+
const maxChars = resolveMaxTextFieldChars(this.maxTextFieldCharsOpt);
|
|
434
|
+
if (typeof patch.input === "string" && patch.input.length > maxChars) {
|
|
435
|
+
patch = { ...patch, input: capText(patch.input, maxChars) };
|
|
436
|
+
}
|
|
437
|
+
if (typeof patch.output === "string" && patch.output.length > maxChars) {
|
|
438
|
+
patch = { ...patch, output: capText(patch.output, maxChars) };
|
|
439
|
+
}
|
|
239
440
|
if (this.debug) {
|
|
240
441
|
console.log(`${this.prefix} queue patch`, {
|
|
241
442
|
eventId,
|
|
@@ -282,9 +483,16 @@ var EventShipper = class {
|
|
|
282
483
|
})));
|
|
283
484
|
}
|
|
284
485
|
async shutdown() {
|
|
285
|
-
|
|
286
|
-
this.
|
|
287
|
-
|
|
486
|
+
this.hasShutdown = true;
|
|
487
|
+
this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
|
|
488
|
+
try {
|
|
489
|
+
for (const t of this.timers.values()) clearTimeout(t);
|
|
490
|
+
this.timers.clear();
|
|
491
|
+
const settled = await raceWithTimeout(this.flush(), SHUTDOWN_DEADLINE_MS);
|
|
492
|
+
if (!settled) this.warnShutdownDrop("in-flight request(s) at shutdown");
|
|
493
|
+
} finally {
|
|
494
|
+
this.shutdownDeadlineAt = void 0;
|
|
495
|
+
}
|
|
288
496
|
}
|
|
289
497
|
async trackSignal(signal) {
|
|
290
498
|
var _a, _b;
|
|
@@ -304,16 +512,21 @@ var EventShipper = class {
|
|
|
304
512
|
}
|
|
305
513
|
}
|
|
306
514
|
];
|
|
515
|
+
if (!this.writeKey) return;
|
|
307
516
|
const url = `${this.baseUrl}signals/track`;
|
|
517
|
+
const opts = this.requestOpts();
|
|
518
|
+
if (!opts) {
|
|
519
|
+
this.warnShutdownDrop("signal");
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
308
522
|
try {
|
|
309
|
-
await postJson(url, body, this.authHeaders(),
|
|
310
|
-
maxAttempts: 3,
|
|
311
|
-
debug: this.debug,
|
|
312
|
-
sdkName: this.sdkName
|
|
313
|
-
});
|
|
523
|
+
await postJson(url, body, this.authHeaders(), opts);
|
|
314
524
|
} catch (err) {
|
|
315
525
|
const msg = err instanceof Error ? err.message : String(err);
|
|
316
|
-
|
|
526
|
+
rateLimitedLog(
|
|
527
|
+
`${this.prefix}.send_signal_failed`,
|
|
528
|
+
() => console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`)
|
|
529
|
+
);
|
|
317
530
|
}
|
|
318
531
|
}
|
|
319
532
|
async identify(users) {
|
|
@@ -334,19 +547,30 @@ var EventShipper = class {
|
|
|
334
547
|
traits: (_a = user.traits) != null ? _a : {}
|
|
335
548
|
};
|
|
336
549
|
});
|
|
550
|
+
if (!this.writeKey) return;
|
|
337
551
|
if (body.length === 0) return;
|
|
338
552
|
const url = `${this.baseUrl}users/identify`;
|
|
553
|
+
const opts = this.requestOpts();
|
|
554
|
+
if (!opts) {
|
|
555
|
+
this.warnShutdownDrop("identify");
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
339
558
|
try {
|
|
340
|
-
await postJson(url, body, this.authHeaders(),
|
|
341
|
-
maxAttempts: 3,
|
|
342
|
-
debug: this.debug,
|
|
343
|
-
sdkName: this.sdkName
|
|
344
|
-
});
|
|
559
|
+
await postJson(url, body, this.authHeaders(), opts);
|
|
345
560
|
} catch (err) {
|
|
346
561
|
const msg = err instanceof Error ? err.message : String(err);
|
|
347
|
-
|
|
562
|
+
rateLimitedLog(
|
|
563
|
+
`${this.prefix}.send_identify_failed`,
|
|
564
|
+
() => console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`)
|
|
565
|
+
);
|
|
348
566
|
}
|
|
349
567
|
}
|
|
568
|
+
warnShutdownDrop(what) {
|
|
569
|
+
rateLimitedLog(
|
|
570
|
+
`${this.prefix}.shutdown_deadline`,
|
|
571
|
+
() => console.warn(`${this.prefix} shutdown flush deadline exceeded; dropping ${what}`)
|
|
572
|
+
);
|
|
573
|
+
}
|
|
350
574
|
async flushOne(eventId) {
|
|
351
575
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
352
576
|
if (!this.enabled) return;
|
|
@@ -410,11 +634,25 @@ var EventShipper = class {
|
|
|
410
634
|
endpoint: url
|
|
411
635
|
});
|
|
412
636
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
637
|
+
if (this.localDebuggerUrl) {
|
|
638
|
+
mirrorPartialEventToLocalDebugger(payload, {
|
|
639
|
+
baseUrl: this.localDebuggerUrl,
|
|
640
|
+
writeKey: this.writeKey,
|
|
641
|
+
debug: this.debug,
|
|
642
|
+
sdkName: this.sdkName
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
if (!this.writeKey) {
|
|
646
|
+
if (!isPending) this.sticky.delete(eventId);
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
const opts = this.requestOpts();
|
|
650
|
+
if (!opts) {
|
|
651
|
+
this.warnShutdownDrop(`track_partial ${eventId}`);
|
|
652
|
+
if (!isPending) this.sticky.delete(eventId);
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
const p = postJson(url, payload, this.authHeaders(), opts);
|
|
418
656
|
this.inFlight.add(p);
|
|
419
657
|
try {
|
|
420
658
|
try {
|
|
@@ -424,7 +662,10 @@ var EventShipper = class {
|
|
|
424
662
|
}
|
|
425
663
|
} catch (err) {
|
|
426
664
|
const msg = err instanceof Error ? err.message : String(err);
|
|
427
|
-
|
|
665
|
+
rateLimitedLog(
|
|
666
|
+
`${this.prefix}.send_track_partial_failed`,
|
|
667
|
+
() => console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`)
|
|
668
|
+
);
|
|
428
669
|
}
|
|
429
670
|
} finally {
|
|
430
671
|
this.inFlight.delete(p);
|
|
@@ -434,28 +675,114 @@ var EventShipper = class {
|
|
|
434
675
|
}
|
|
435
676
|
}
|
|
436
677
|
};
|
|
437
|
-
var
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
678
|
+
var DEFAULT_SECRET_KEY_NAMES = [
|
|
679
|
+
"apikey",
|
|
680
|
+
"apisecret",
|
|
681
|
+
"apitoken",
|
|
682
|
+
"secretaccesskey",
|
|
683
|
+
"sessiontoken",
|
|
684
|
+
"privatekey",
|
|
685
|
+
"privatekeyid",
|
|
686
|
+
"clientsecret",
|
|
687
|
+
"accesstoken",
|
|
688
|
+
"refreshtoken",
|
|
689
|
+
"oauthtoken",
|
|
690
|
+
"bearertoken",
|
|
691
|
+
"authorization",
|
|
692
|
+
"password",
|
|
693
|
+
"passphrase"
|
|
694
|
+
];
|
|
695
|
+
var REDACTED_PLACEHOLDER = "[REDACTED]";
|
|
696
|
+
function normalizeKeyName(name) {
|
|
697
|
+
return name.toLowerCase().replace(/[-_.]/g, "");
|
|
698
|
+
}
|
|
699
|
+
function redactSecretsInObject(value, options) {
|
|
700
|
+
var _a, _b;
|
|
701
|
+
const normalizedSecretSet = buildSecretSet((_a = options == null ? void 0 : options.secretKeyNames) != null ? _a : DEFAULT_SECRET_KEY_NAMES);
|
|
702
|
+
const placeholder = (_b = options == null ? void 0 : options.placeholder) != null ? _b : REDACTED_PLACEHOLDER;
|
|
703
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
704
|
+
const walk = (node) => {
|
|
705
|
+
if (node === null || typeof node !== "object") return node;
|
|
706
|
+
if (seen.has(node)) return "[CIRCULAR]";
|
|
707
|
+
seen.add(node);
|
|
708
|
+
if (Array.isArray(node)) {
|
|
709
|
+
return node.map((item) => walk(item));
|
|
710
|
+
}
|
|
711
|
+
const out = {};
|
|
712
|
+
for (const [k, v] of Object.entries(node)) {
|
|
713
|
+
if (normalizedSecretSet.has(normalizeKeyName(k))) {
|
|
714
|
+
out[k] = placeholder;
|
|
715
|
+
} else {
|
|
716
|
+
out[k] = walk(v);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
return out;
|
|
720
|
+
};
|
|
721
|
+
return walk(value);
|
|
722
|
+
}
|
|
723
|
+
function buildSecretSet(names) {
|
|
724
|
+
const set = /* @__PURE__ */ new Set();
|
|
725
|
+
for (const name of names) set.add(normalizeKeyName(name));
|
|
726
|
+
return set;
|
|
727
|
+
}
|
|
728
|
+
var DEFAULT_REDACT_ATTRIBUTE_KEYS = [
|
|
729
|
+
"ai.request.providerOptions",
|
|
730
|
+
"ai.response.providerMetadata"
|
|
731
|
+
];
|
|
732
|
+
function defaultTransformSpan(span) {
|
|
733
|
+
const attrs = span.attributes;
|
|
734
|
+
if (!attrs || attrs.length === 0) return span;
|
|
735
|
+
let nextAttrs;
|
|
736
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
737
|
+
const attr = attrs[i];
|
|
738
|
+
const redacted = redactJsonAttributeValue(attr.key, attr.value);
|
|
739
|
+
if (redacted === void 0) continue;
|
|
740
|
+
if (!nextAttrs) nextAttrs = attrs.slice();
|
|
741
|
+
nextAttrs[i] = { key: attr.key, value: redacted };
|
|
742
|
+
}
|
|
743
|
+
if (!nextAttrs) return span;
|
|
744
|
+
return { ...span, attributes: nextAttrs };
|
|
745
|
+
}
|
|
746
|
+
var REDACT_JSON_ATTRIBUTE_KEYS = new Set(DEFAULT_REDACT_ATTRIBUTE_KEYS);
|
|
747
|
+
function redactJsonAttributeValue(key, value) {
|
|
748
|
+
if (!REDACT_JSON_ATTRIBUTE_KEYS.has(key)) return void 0;
|
|
749
|
+
const json = value.stringValue;
|
|
750
|
+
if (typeof json !== "string" || json.length === 0) return void 0;
|
|
751
|
+
let parsed;
|
|
752
|
+
try {
|
|
753
|
+
parsed = JSON.parse(json);
|
|
754
|
+
} catch (e) {
|
|
755
|
+
return void 0;
|
|
756
|
+
}
|
|
757
|
+
const scrubbed = redactSecretsInObject(parsed);
|
|
758
|
+
let scrubbedJson;
|
|
759
|
+
try {
|
|
760
|
+
scrubbedJson = JSON.stringify(scrubbed);
|
|
761
|
+
} catch (e) {
|
|
762
|
+
return void 0;
|
|
763
|
+
}
|
|
764
|
+
if (scrubbedJson === json) return void 0;
|
|
765
|
+
return { stringValue: scrubbedJson };
|
|
442
766
|
}
|
|
443
|
-
function
|
|
444
|
-
var _a;
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
})
|
|
767
|
+
function applyOtelSpanAttributeLimit(limit) {
|
|
768
|
+
var _a, _b;
|
|
769
|
+
try {
|
|
770
|
+
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;
|
|
771
|
+
if (!raw) return limit;
|
|
772
|
+
const parsed = Number.parseInt(raw, 10);
|
|
773
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
774
|
+
return Math.min(limit, parsed);
|
|
775
|
+
}
|
|
776
|
+
} catch (e) {
|
|
777
|
+
}
|
|
778
|
+
return limit;
|
|
453
779
|
}
|
|
454
780
|
var TraceShipper = class {
|
|
455
781
|
constructor(opts) {
|
|
456
782
|
this.queue = [];
|
|
457
783
|
this.inFlight = /* @__PURE__ */ new Set();
|
|
458
|
-
|
|
784
|
+
this.hasShutdown = false;
|
|
785
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
459
786
|
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
460
787
|
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
461
788
|
this.enabled = opts.enabled !== false;
|
|
@@ -468,13 +795,79 @@ var TraceShipper = class {
|
|
|
468
795
|
this.prefix = `[raindrop-ai/${this.sdkName}]`;
|
|
469
796
|
this.serviceName = (_g = opts.serviceName) != null ? _g : "raindrop.core";
|
|
470
797
|
this.serviceVersion = (_h = opts.serviceVersion) != null ? _h : "0.0.0";
|
|
471
|
-
|
|
472
|
-
if (
|
|
473
|
-
this.
|
|
474
|
-
|
|
475
|
-
|
|
798
|
+
this.localDebuggerUrl = (_i = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _i : void 0;
|
|
799
|
+
if (this.debug && this.localDebuggerUrl) {
|
|
800
|
+
console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
|
|
801
|
+
}
|
|
802
|
+
this.transformSpanHook = opts.transformSpan;
|
|
803
|
+
this.disableDefaultRedaction = opts.disableDefaultRedaction === true;
|
|
804
|
+
this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Cap every string attribute value on the span. O(#attributes) length
|
|
808
|
+
* checks; only oversized values pay a slice. Runs AFTER the redaction
|
|
809
|
+
* pipeline so the default secret-scrub still sees parseable JSON in
|
|
810
|
+
* `ai.request.providerOptions` / `ai.response.providerMetadata` (capping
|
|
811
|
+
* first could cut a JSON blob mid-way, fail the parse, and ship secrets
|
|
812
|
+
* in the surviving prefix).
|
|
813
|
+
*
|
|
814
|
+
* A stricter `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var is honored
|
|
815
|
+
* for span content, matching the Python SDK and the OTel SDK convention.
|
|
816
|
+
*/
|
|
817
|
+
capSpanAttributes(span) {
|
|
818
|
+
var _a;
|
|
819
|
+
const maxChars = applyOtelSpanAttributeLimit(
|
|
820
|
+
resolveMaxTextFieldChars(this.maxTextFieldCharsOpt)
|
|
821
|
+
);
|
|
822
|
+
const attrs = span.attributes;
|
|
823
|
+
if (!attrs || attrs.length === 0) return span;
|
|
824
|
+
let nextAttrs;
|
|
825
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
826
|
+
const attr = attrs[i];
|
|
827
|
+
const value = (_a = attr.value) == null ? void 0 : _a.stringValue;
|
|
828
|
+
if (typeof value !== "string" || value.length <= maxChars) continue;
|
|
829
|
+
if (!nextAttrs) nextAttrs = attrs.slice();
|
|
830
|
+
nextAttrs[i] = {
|
|
831
|
+
key: attr.key,
|
|
832
|
+
value: { ...attr.value, stringValue: capText(value, maxChars) }
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
if (!nextAttrs) return span;
|
|
836
|
+
return { ...span, attributes: nextAttrs };
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Apply the user `transformSpan` hook (if any) followed by the default
|
|
840
|
+
* redactor (unless disabled). Returns either the (possibly new) span to
|
|
841
|
+
* ship, or `null` to drop the span entirely.
|
|
842
|
+
*
|
|
843
|
+
* Ordering: user hook runs first so callers can rewrite the span freely
|
|
844
|
+
* (rename attrs, add new ones, scrub things the default doesn't know
|
|
845
|
+
* about). The default redactor then runs on whatever the user produced,
|
|
846
|
+
* acting as the always-on floor for documented BYOK secrets. If the user
|
|
847
|
+
* sets `disableDefaultRedaction: true`, the floor is skipped.
|
|
848
|
+
*
|
|
849
|
+
* Fail-closed: if the user hook throws, the span is dropped — a buggy
|
|
850
|
+
* hook can never accidentally ship raw, un-redacted spans.
|
|
851
|
+
*/
|
|
852
|
+
redactSpan(span) {
|
|
853
|
+
let current = span;
|
|
854
|
+
if (this.transformSpanHook) {
|
|
855
|
+
try {
|
|
856
|
+
const result = this.transformSpanHook(current);
|
|
857
|
+
if (result === null) return null;
|
|
858
|
+
if (result !== void 0) current = result;
|
|
859
|
+
} catch (err) {
|
|
860
|
+
if (this.debug) {
|
|
861
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
862
|
+
console.warn(`${this.prefix} transformSpan hook threw: ${msg}`);
|
|
863
|
+
}
|
|
864
|
+
return null;
|
|
476
865
|
}
|
|
477
866
|
}
|
|
867
|
+
if (!this.disableDefaultRedaction) {
|
|
868
|
+
current = defaultTransformSpan(current);
|
|
869
|
+
}
|
|
870
|
+
return this.capSpanAttributes(current);
|
|
478
871
|
}
|
|
479
872
|
isDebugEnabled() {
|
|
480
873
|
return this.debug;
|
|
@@ -492,8 +885,8 @@ var TraceShipper = class {
|
|
|
492
885
|
];
|
|
493
886
|
if ((_b = args.attributes) == null ? void 0 : _b.length) attrs.push(...args.attributes);
|
|
494
887
|
const span = { ids, name: args.name, startTimeUnixNano: started, attributes: attrs };
|
|
495
|
-
|
|
496
|
-
|
|
888
|
+
this.mirrorToLocalDebugger(
|
|
889
|
+
buildOtlpSpan({
|
|
497
890
|
ids: span.ids,
|
|
498
891
|
name: span.name,
|
|
499
892
|
startTimeUnixNano: span.startTimeUnixNano,
|
|
@@ -501,16 +894,21 @@ var TraceShipper = class {
|
|
|
501
894
|
// placeholder — will be updated on endSpan
|
|
502
895
|
attributes: span.attributes,
|
|
503
896
|
status: { code: SpanStatusCode.UNSET }
|
|
504
|
-
})
|
|
505
|
-
|
|
506
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
507
|
-
baseUrl: this.localDebuggerUrl,
|
|
508
|
-
debug: false,
|
|
509
|
-
sdkName: this.sdkName
|
|
510
|
-
});
|
|
511
|
-
}
|
|
897
|
+
})
|
|
898
|
+
);
|
|
512
899
|
return span;
|
|
513
900
|
}
|
|
901
|
+
mirrorToLocalDebugger(span) {
|
|
902
|
+
if (!this.localDebuggerUrl) return;
|
|
903
|
+
const redacted = this.redactSpan(span);
|
|
904
|
+
if (redacted === null) return;
|
|
905
|
+
const body = buildExportTraceServiceRequest([redacted], this.serviceName, this.serviceVersion);
|
|
906
|
+
mirrorTraceExportToLocalDebugger(body, {
|
|
907
|
+
baseUrl: this.localDebuggerUrl,
|
|
908
|
+
debug: false,
|
|
909
|
+
sdkName: this.sdkName
|
|
910
|
+
});
|
|
911
|
+
}
|
|
514
912
|
endSpan(span, extra) {
|
|
515
913
|
var _a, _b;
|
|
516
914
|
if (span.endTimeUnixNano) return;
|
|
@@ -532,14 +930,7 @@ var TraceShipper = class {
|
|
|
532
930
|
status
|
|
533
931
|
});
|
|
534
932
|
this.enqueue(otlp);
|
|
535
|
-
|
|
536
|
-
const body = buildExportTraceServiceRequest([otlp], this.serviceName, this.serviceVersion);
|
|
537
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
538
|
-
baseUrl: this.localDebuggerUrl,
|
|
539
|
-
debug: false,
|
|
540
|
-
sdkName: this.sdkName
|
|
541
|
-
});
|
|
542
|
-
}
|
|
933
|
+
this.mirrorToLocalDebugger(otlp);
|
|
543
934
|
}
|
|
544
935
|
createSpan(args) {
|
|
545
936
|
var _a;
|
|
@@ -557,14 +948,7 @@ var TraceShipper = class {
|
|
|
557
948
|
status: args.status
|
|
558
949
|
});
|
|
559
950
|
this.enqueue(otlp);
|
|
560
|
-
|
|
561
|
-
const body = buildExportTraceServiceRequest([otlp], this.serviceName, this.serviceVersion);
|
|
562
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
563
|
-
baseUrl: this.localDebuggerUrl,
|
|
564
|
-
debug: false,
|
|
565
|
-
sdkName: this.sdkName
|
|
566
|
-
});
|
|
567
|
-
}
|
|
951
|
+
this.mirrorToLocalDebugger(otlp);
|
|
568
952
|
}
|
|
569
953
|
enqueue(span) {
|
|
570
954
|
if (!this.enabled) return;
|
|
@@ -576,10 +960,12 @@ var TraceShipper = class {
|
|
|
576
960
|
)}`
|
|
577
961
|
);
|
|
578
962
|
}
|
|
963
|
+
const redacted = this.redactSpan(span);
|
|
964
|
+
if (redacted === null) return;
|
|
579
965
|
if (this.queue.length >= this.maxQueueSize) {
|
|
580
966
|
this.queue.shift();
|
|
581
967
|
}
|
|
582
|
-
this.queue.push(
|
|
968
|
+
this.queue.push(redacted);
|
|
583
969
|
if (this.queue.length >= this.maxBatchSize) {
|
|
584
970
|
void this.flush().catch(() => {
|
|
585
971
|
});
|
|
@@ -601,6 +987,17 @@ var TraceShipper = class {
|
|
|
601
987
|
}
|
|
602
988
|
while (this.queue.length > 0) {
|
|
603
989
|
const batch = this.queue.splice(0, this.maxBatchSize);
|
|
990
|
+
if (!this.writeKey) continue;
|
|
991
|
+
const opts = this.requestOpts();
|
|
992
|
+
if (!opts) {
|
|
993
|
+
rateLimitedLog(
|
|
994
|
+
`${this.prefix}.shutdown_deadline`,
|
|
995
|
+
() => console.warn(
|
|
996
|
+
`${this.prefix} shutdown flush deadline exceeded; dropping ${batch.length} spans`
|
|
997
|
+
)
|
|
998
|
+
);
|
|
999
|
+
continue;
|
|
1000
|
+
}
|
|
604
1001
|
const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
|
|
605
1002
|
const url = `${this.baseUrl}traces`;
|
|
606
1003
|
if (this.debug) {
|
|
@@ -609,11 +1006,7 @@ var TraceShipper = class {
|
|
|
609
1006
|
endpoint: url
|
|
610
1007
|
});
|
|
611
1008
|
}
|
|
612
|
-
const p = postJson(url, body, this.authHeaders(),
|
|
613
|
-
maxAttempts: 3,
|
|
614
|
-
debug: this.debug,
|
|
615
|
-
sdkName: this.sdkName
|
|
616
|
-
});
|
|
1009
|
+
const p = postJson(url, body, this.authHeaders(), opts);
|
|
617
1010
|
this.inFlight.add(p);
|
|
618
1011
|
try {
|
|
619
1012
|
try {
|
|
@@ -621,21 +1014,61 @@ var TraceShipper = class {
|
|
|
621
1014
|
if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
|
|
622
1015
|
} catch (err) {
|
|
623
1016
|
const msg = err instanceof Error ? err.message : String(err);
|
|
624
|
-
|
|
1017
|
+
rateLimitedLog(
|
|
1018
|
+
`${this.prefix}.send_spans_failed`,
|
|
1019
|
+
() => console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`)
|
|
1020
|
+
);
|
|
625
1021
|
}
|
|
626
1022
|
} finally {
|
|
627
1023
|
this.inFlight.delete(p);
|
|
628
1024
|
}
|
|
629
1025
|
}
|
|
630
1026
|
}
|
|
1027
|
+
/** See EventShipper.requestOpts — same shutdown-budget semantics. */
|
|
1028
|
+
requestOpts() {
|
|
1029
|
+
if (this.shutdownDeadlineAt !== void 0) {
|
|
1030
|
+
const remainingMs = this.shutdownDeadlineAt - Date.now();
|
|
1031
|
+
if (remainingMs <= 0) return null;
|
|
1032
|
+
return {
|
|
1033
|
+
maxAttempts: 1,
|
|
1034
|
+
debug: this.debug,
|
|
1035
|
+
sdkName: this.sdkName,
|
|
1036
|
+
timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
|
|
1037
|
+
};
|
|
1038
|
+
}
|
|
1039
|
+
if (this.hasShutdown) {
|
|
1040
|
+
return {
|
|
1041
|
+
maxAttempts: 1,
|
|
1042
|
+
debug: this.debug,
|
|
1043
|
+
sdkName: this.sdkName,
|
|
1044
|
+
timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
|
|
1048
|
+
}
|
|
631
1049
|
async shutdown() {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
1050
|
+
this.hasShutdown = true;
|
|
1051
|
+
this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
|
|
1052
|
+
try {
|
|
1053
|
+
if (this.timer) {
|
|
1054
|
+
clearTimeout(this.timer);
|
|
1055
|
+
this.timer = void 0;
|
|
1056
|
+
}
|
|
1057
|
+
const drain = async () => {
|
|
1058
|
+
await this.flush();
|
|
1059
|
+
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
1060
|
+
})));
|
|
1061
|
+
};
|
|
1062
|
+
const settled = await raceWithTimeout(drain(), SHUTDOWN_DEADLINE_MS);
|
|
1063
|
+
if (!settled) {
|
|
1064
|
+
rateLimitedLog(
|
|
1065
|
+
`${this.prefix}.shutdown_deadline`,
|
|
1066
|
+
() => console.warn(`${this.prefix} shutdown flush deadline exceeded; abandoning in-flight spans`)
|
|
1067
|
+
);
|
|
1068
|
+
}
|
|
1069
|
+
} finally {
|
|
1070
|
+
this.shutdownDeadlineAt = void 0;
|
|
635
1071
|
}
|
|
636
|
-
await this.flush();
|
|
637
|
-
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
638
|
-
})));
|
|
639
1072
|
}
|
|
640
1073
|
};
|
|
641
1074
|
|
|
@@ -984,18 +1417,85 @@ function transcriptToProperties(summary) {
|
|
|
984
1417
|
|
|
985
1418
|
// src/event-mapper.ts
|
|
986
1419
|
var MAX_ATTR_LENGTH = 32768;
|
|
1420
|
+
var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
|
|
1421
|
+
var MAX_TEXT_FIELD_CHARS = 1e6;
|
|
1422
|
+
var MAX_BOUNDED_DEPTH = 12;
|
|
987
1423
|
function truncate(value) {
|
|
988
1424
|
if (value === void 0) return void 0;
|
|
989
1425
|
if (value.length <= MAX_ATTR_LENGTH) return value;
|
|
990
1426
|
const suffix = "\n...[truncated]";
|
|
991
1427
|
return value.slice(0, MAX_ATTR_LENGTH - suffix.length) + suffix;
|
|
992
1428
|
}
|
|
1429
|
+
function capText2(value, limit = MAX_TEXT_FIELD_CHARS) {
|
|
1430
|
+
if (value.length <= limit) return value;
|
|
1431
|
+
if (limit > TRUNCATION_MARKER2.length) {
|
|
1432
|
+
return value.slice(0, limit - TRUNCATION_MARKER2.length) + TRUNCATION_MARKER2;
|
|
1433
|
+
}
|
|
1434
|
+
return value.slice(0, limit);
|
|
1435
|
+
}
|
|
1436
|
+
function boundedClone2(value, budget, depth) {
|
|
1437
|
+
if (budget.remaining <= 0) return TRUNCATION_MARKER2;
|
|
1438
|
+
if (typeof value === "string") {
|
|
1439
|
+
if (value.length > budget.remaining) {
|
|
1440
|
+
const taken = value.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
|
|
1441
|
+
budget.remaining = 0;
|
|
1442
|
+
return taken;
|
|
1443
|
+
}
|
|
1444
|
+
budget.remaining -= Math.max(value.length, 1);
|
|
1445
|
+
return value;
|
|
1446
|
+
}
|
|
1447
|
+
if (value === null || typeof value === "number" || typeof value === "boolean") {
|
|
1448
|
+
budget.remaining -= 8;
|
|
1449
|
+
return value;
|
|
1450
|
+
}
|
|
1451
|
+
if (typeof value !== "object") {
|
|
1452
|
+
budget.remaining -= 8;
|
|
1453
|
+
return value;
|
|
1454
|
+
}
|
|
1455
|
+
if (depth >= MAX_BOUNDED_DEPTH) {
|
|
1456
|
+
budget.remaining -= 16;
|
|
1457
|
+
return `<max depth: ${TRUNCATION_MARKER2}>`;
|
|
1458
|
+
}
|
|
1459
|
+
const withToJson = value;
|
|
1460
|
+
if (typeof withToJson.toJSON === "function") {
|
|
1461
|
+
try {
|
|
1462
|
+
return boundedClone2(withToJson.toJSON(), budget, depth + 1);
|
|
1463
|
+
} catch (e) {
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
if (Array.isArray(value)) {
|
|
1467
|
+
const out2 = [];
|
|
1468
|
+
for (const item of value) {
|
|
1469
|
+
if (budget.remaining <= 0) {
|
|
1470
|
+
out2.push(TRUNCATION_MARKER2);
|
|
1471
|
+
break;
|
|
1472
|
+
}
|
|
1473
|
+
out2.push(boundedClone2(item, budget, depth + 1));
|
|
1474
|
+
}
|
|
1475
|
+
return out2;
|
|
1476
|
+
}
|
|
1477
|
+
const out = {};
|
|
1478
|
+
for (const key of Object.keys(value)) {
|
|
1479
|
+
if (budget.remaining <= 0) {
|
|
1480
|
+
out["..."] = TRUNCATION_MARKER2;
|
|
1481
|
+
break;
|
|
1482
|
+
}
|
|
1483
|
+
budget.remaining -= Math.max(key.length, 1);
|
|
1484
|
+
out[key] = boundedClone2(value[key], budget, depth + 1);
|
|
1485
|
+
}
|
|
1486
|
+
return out;
|
|
1487
|
+
}
|
|
993
1488
|
function safeStringify(value) {
|
|
994
1489
|
if (value === void 0 || value === null) return void 0;
|
|
995
1490
|
try {
|
|
996
|
-
|
|
1491
|
+
const pruned = boundedClone2(
|
|
1492
|
+
value,
|
|
1493
|
+
{ remaining: MAX_ATTR_LENGTH + TRUNCATION_MARKER2.length + 256 },
|
|
1494
|
+
0
|
|
1495
|
+
);
|
|
1496
|
+
return truncate(JSON.stringify(pruned));
|
|
997
1497
|
} catch (e) {
|
|
998
|
-
return truncate(String(value));
|
|
1498
|
+
return truncate(capText2(String(value), MAX_ATTR_LENGTH + 16));
|
|
999
1499
|
}
|
|
1000
1500
|
}
|
|
1001
1501
|
var STATE_DIR = join2(tmpdir(), "raindrop-claude-code");
|
|
@@ -1176,7 +1676,9 @@ async function handleUserPromptSubmit(payload, convoId, config, properties, even
|
|
|
1176
1676
|
userId: config.userId,
|
|
1177
1677
|
convoId,
|
|
1178
1678
|
eventName: config.eventName,
|
|
1179
|
-
|
|
1679
|
+
// Capped so a pasted multi-MB prompt can't produce an event that the
|
|
1680
|
+
// ingest API rejects (silent data loss) — it lands truncated instead.
|
|
1681
|
+
input: payload.prompt !== void 0 ? capText2(payload.prompt) : void 0,
|
|
1180
1682
|
model,
|
|
1181
1683
|
properties
|
|
1182
1684
|
};
|
|
@@ -1299,7 +1801,7 @@ async function handlePostCompact(payload, eventId, convoId, config, properties,
|
|
|
1299
1801
|
properties: {
|
|
1300
1802
|
...properties,
|
|
1301
1803
|
compaction_trigger: payload.trigger,
|
|
1302
|
-
compact_summary: payload.compact_summary
|
|
1804
|
+
compact_summary: payload.compact_summary !== void 0 ? capText2(payload.compact_summary) : void 0
|
|
1303
1805
|
}
|
|
1304
1806
|
});
|
|
1305
1807
|
}
|
|
@@ -1534,7 +2036,7 @@ function enrichFromTranscript(payload, eventId, traceShipper) {
|
|
|
1534
2036
|
attrInt("gen_ai.usage.prompt_tokens", phase.inputTokens),
|
|
1535
2037
|
attrInt("gen_ai.usage.completion_tokens", phase.outputTokens),
|
|
1536
2038
|
...phase.cacheReadTokens > 0 ? [attrInt("gen_ai.usage.cache_read_tokens", phase.cacheReadTokens)] : [],
|
|
1537
|
-
attrString("ai.response.text", outputText),
|
|
2039
|
+
attrString("ai.response.text", truncate(outputText)),
|
|
1538
2040
|
...toolCallNames ? [attrString("ai.toolCall.names", toolCallNames)] : [],
|
|
1539
2041
|
...phase.hasThinking ? [attrString("ai.has_thinking", "true")] : [],
|
|
1540
2042
|
attrInt("ai.llm_call.index", emittedPhaseIndex)
|
|
@@ -1594,7 +2096,7 @@ async function handleStopOrFailure(payload, eventId, convoId, config, properties
|
|
|
1594
2096
|
userId: config.userId,
|
|
1595
2097
|
convoId,
|
|
1596
2098
|
eventName: config.eventName,
|
|
1597
|
-
output,
|
|
2099
|
+
output: output !== void 0 ? capText2(output) : void 0,
|
|
1598
2100
|
...(summary == null ? void 0 : summary.model) ? { model: summary.model } : {},
|
|
1599
2101
|
properties: {
|
|
1600
2102
|
...properties,
|
|
@@ -1625,6 +2127,7 @@ import { join as join3 } from "path";
|
|
|
1625
2127
|
var DEFAULT_PORT = 5899;
|
|
1626
2128
|
var DEFAULT_URL = `http://localhost:${DEFAULT_PORT}/v1/`;
|
|
1627
2129
|
var HEALTH_TIMEOUT_MS = 300;
|
|
2130
|
+
var MIRROR_TIMEOUT_MS = 2e3;
|
|
1628
2131
|
var CACHE_DIR = join3(tmpdir2(), "raindrop-claude-code");
|
|
1629
2132
|
var CACHE_FILE = join3(CACHE_DIR, "debugger_cache");
|
|
1630
2133
|
var CACHE_TTL_MS = 5e3;
|
|
@@ -1648,10 +2151,8 @@ function writeCache(url) {
|
|
|
1648
2151
|
async function healthPing(baseUrl) {
|
|
1649
2152
|
const healthUrl = baseUrl.replace(/\/v1\/$/, "/health");
|
|
1650
2153
|
try {
|
|
1651
|
-
const
|
|
1652
|
-
const
|
|
1653
|
-
const resp = await fetch(healthUrl, { signal: controller.signal });
|
|
1654
|
-
clearTimeout(timeout);
|
|
2154
|
+
const signal = AbortSignal.timeout(HEALTH_TIMEOUT_MS);
|
|
2155
|
+
const resp = await fetch(healthUrl, { signal });
|
|
1655
2156
|
if (resp.ok) {
|
|
1656
2157
|
const body = await resp.json();
|
|
1657
2158
|
if (body.ok) return baseUrl;
|
|
@@ -1694,7 +2195,8 @@ function mirrorEventToLocalDebugger(baseUrl, payload, debug) {
|
|
|
1694
2195
|
void fetch(url, {
|
|
1695
2196
|
method: "POST",
|
|
1696
2197
|
headers: { "Content-Type": "application/json" },
|
|
1697
|
-
body: JSON.stringify(payload)
|
|
2198
|
+
body: JSON.stringify(payload),
|
|
2199
|
+
signal: AbortSignal.timeout(MIRROR_TIMEOUT_MS)
|
|
1698
2200
|
}).catch(() => {
|
|
1699
2201
|
});
|
|
1700
2202
|
}
|