@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/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ import { randomUUID as randomUUID2 } from "crypto";
|
|
|
7
7
|
import { tmpdir } from "os";
|
|
8
8
|
import { join } from "path";
|
|
9
9
|
|
|
10
|
-
// ../core/dist/chunk-
|
|
10
|
+
// ../core/dist/chunk-U5HUTMR5.js
|
|
11
11
|
function getCrypto() {
|
|
12
12
|
const c = globalThis.crypto;
|
|
13
13
|
return c;
|
|
@@ -51,6 +51,8 @@ function base64Encode(bytes) {
|
|
|
51
51
|
}
|
|
52
52
|
return out;
|
|
53
53
|
}
|
|
54
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
55
|
+
var MAX_RETRY_DELAY_MS = 3e4;
|
|
54
56
|
function wait(ms) {
|
|
55
57
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
56
58
|
}
|
|
@@ -58,6 +60,46 @@ function formatEndpoint(endpoint) {
|
|
|
58
60
|
if (!endpoint) return void 0;
|
|
59
61
|
return endpoint.endsWith("/") ? endpoint : `${endpoint}/`;
|
|
60
62
|
}
|
|
63
|
+
function redactUrlForLog(url) {
|
|
64
|
+
try {
|
|
65
|
+
const parsed = new URL(url);
|
|
66
|
+
parsed.username = "";
|
|
67
|
+
parsed.password = "";
|
|
68
|
+
parsed.search = "";
|
|
69
|
+
parsed.hash = "";
|
|
70
|
+
return parsed.toString();
|
|
71
|
+
} catch (e) {
|
|
72
|
+
return "<unparseable-url>";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
var RATE_LIMITED_LOG_INTERVAL_MS = 3e4;
|
|
76
|
+
var rateLimitedLogLast = /* @__PURE__ */ new Map();
|
|
77
|
+
function rateLimitedLog(key, log) {
|
|
78
|
+
const now = Date.now();
|
|
79
|
+
const last = rateLimitedLogLast.get(key);
|
|
80
|
+
if (last !== void 0 && now - last < RATE_LIMITED_LOG_INTERVAL_MS) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
rateLimitedLogLast.set(key, now);
|
|
84
|
+
log();
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
async function raceWithTimeout(promise, timeoutMs) {
|
|
88
|
+
let timer;
|
|
89
|
+
const settledInTime = await Promise.race([
|
|
90
|
+
promise.then(
|
|
91
|
+
() => true,
|
|
92
|
+
() => true
|
|
93
|
+
),
|
|
94
|
+
new Promise((resolve2) => {
|
|
95
|
+
var _a;
|
|
96
|
+
timer = setTimeout(() => resolve2(false), Math.max(0, timeoutMs));
|
|
97
|
+
(_a = timer.unref) == null ? void 0 : _a.call(timer);
|
|
98
|
+
})
|
|
99
|
+
]);
|
|
100
|
+
if (timer) clearTimeout(timer);
|
|
101
|
+
return settledInTime;
|
|
102
|
+
}
|
|
61
103
|
function parseRetryAfter(headers) {
|
|
62
104
|
var _a;
|
|
63
105
|
const value = (_a = headers.get("Retry-After")) != null ? _a : headers.get("retry-after");
|
|
@@ -74,12 +116,12 @@ function parseRetryAfter(headers) {
|
|
|
74
116
|
function getRetryDelayMs(attemptNumber, previousError) {
|
|
75
117
|
if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
|
|
76
118
|
const v = previousError.retryAfterMs;
|
|
77
|
-
if (typeof v === "number") return Math.max(0, v);
|
|
119
|
+
if (typeof v === "number") return Math.min(Math.max(0, v), MAX_RETRY_DELAY_MS);
|
|
78
120
|
}
|
|
79
121
|
if (attemptNumber <= 1) return 0;
|
|
80
122
|
const base = 500;
|
|
81
123
|
const factor = Math.pow(2, attemptNumber - 2);
|
|
82
|
-
return base * factor;
|
|
124
|
+
return Math.min(base * factor, MAX_RETRY_DELAY_MS);
|
|
83
125
|
}
|
|
84
126
|
async function withRetry(operation, opName, opts) {
|
|
85
127
|
const prefix = opts.sdkName ? `[raindrop-ai/${opts.sdkName}]` : "[raindrop-ai/core]";
|
|
@@ -114,7 +156,9 @@ async function withRetry(operation, opName, opts) {
|
|
|
114
156
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
115
157
|
}
|
|
116
158
|
async function postJson(url, body, headers, opts) {
|
|
117
|
-
|
|
159
|
+
var _a;
|
|
160
|
+
const opName = `POST ${redactUrlForLog(url)}`;
|
|
161
|
+
const timeoutMs = (_a = opts.timeoutMs) != null ? _a : DEFAULT_REQUEST_TIMEOUT_MS;
|
|
118
162
|
await withRetry(
|
|
119
163
|
async () => {
|
|
120
164
|
const resp = await fetch(url, {
|
|
@@ -123,7 +167,8 @@ async function postJson(url, body, headers, opts) {
|
|
|
123
167
|
"Content-Type": "application/json",
|
|
124
168
|
...headers
|
|
125
169
|
},
|
|
126
|
-
body: JSON.stringify(body)
|
|
170
|
+
body: JSON.stringify(body),
|
|
171
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
127
172
|
});
|
|
128
173
|
if (!resp.ok) {
|
|
129
174
|
const text = await resp.text().catch(() => "");
|
|
@@ -140,6 +185,27 @@ async function postJson(url, body, headers, opts) {
|
|
|
140
185
|
opts
|
|
141
186
|
);
|
|
142
187
|
}
|
|
188
|
+
var DEFAULT_MAX_TEXT_FIELD_CHARS = 1e6;
|
|
189
|
+
var TRUNCATION_MARKER = "...[truncated by raindrop]";
|
|
190
|
+
var currentDefaultMaxTextFieldChars = DEFAULT_MAX_TEXT_FIELD_CHARS;
|
|
191
|
+
function resolveMaxTextFieldChars(value) {
|
|
192
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
193
|
+
return Math.floor(value);
|
|
194
|
+
}
|
|
195
|
+
return currentDefaultMaxTextFieldChars;
|
|
196
|
+
}
|
|
197
|
+
function truncateToLimit(text, limit) {
|
|
198
|
+
if (limit > TRUNCATION_MARKER.length) {
|
|
199
|
+
return text.slice(0, limit - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;
|
|
200
|
+
}
|
|
201
|
+
return text.slice(0, Math.max(0, limit));
|
|
202
|
+
}
|
|
203
|
+
function capText(value, limit) {
|
|
204
|
+
if (typeof value !== "string") return value;
|
|
205
|
+
const max = limit != null ? limit : currentDefaultMaxTextFieldChars;
|
|
206
|
+
if (value.length <= max) return value;
|
|
207
|
+
return truncateToLimit(value, max);
|
|
208
|
+
}
|
|
143
209
|
var SpanStatusCode = {
|
|
144
210
|
UNSET: 0,
|
|
145
211
|
OK: 1,
|
|
@@ -197,6 +263,93 @@ function buildExportTraceServiceRequest(spans, serviceName = "raindrop.core", se
|
|
|
197
263
|
]
|
|
198
264
|
};
|
|
199
265
|
}
|
|
266
|
+
var LOCAL_DEBUGGER_ENV_VAR = "RAINDROP_LOCAL_DEBUGGER";
|
|
267
|
+
var WORKSHOP_ENV_VAR = "RAINDROP_WORKSHOP";
|
|
268
|
+
var DEFAULT_LOCAL_WORKSHOP_URL = "http://localhost:5899/v1/";
|
|
269
|
+
function readEnvVar(name) {
|
|
270
|
+
var _a;
|
|
271
|
+
try {
|
|
272
|
+
const env = (_a = globalThis == null ? void 0 : globalThis.process) == null ? void 0 : _a.env;
|
|
273
|
+
if (env && typeof env[name] === "string" && env[name].length > 0) {
|
|
274
|
+
return env[name];
|
|
275
|
+
}
|
|
276
|
+
} catch (e) {
|
|
277
|
+
}
|
|
278
|
+
return void 0;
|
|
279
|
+
}
|
|
280
|
+
function readWorkshopEnv() {
|
|
281
|
+
const raw = readEnvVar(WORKSHOP_ENV_VAR);
|
|
282
|
+
if (raw === void 0) return void 0;
|
|
283
|
+
const trimmed = raw.trim();
|
|
284
|
+
if (trimmed.length === 0) return void 0;
|
|
285
|
+
if (/^https?:\/\//i.test(trimmed)) return { url: trimmed };
|
|
286
|
+
if (/^(1|true|yes|on)$/i.test(trimmed)) return "enable";
|
|
287
|
+
if (/^(0|false|no|off)$/i.test(trimmed)) return "disable";
|
|
288
|
+
return void 0;
|
|
289
|
+
}
|
|
290
|
+
function isLocalDevHost(hostname) {
|
|
291
|
+
if (!hostname) return false;
|
|
292
|
+
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "0.0.0.0" || hostname === "::1") {
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
if (hostname.endsWith(".localhost")) return true;
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
function readRuntimeHostname() {
|
|
299
|
+
try {
|
|
300
|
+
const loc = globalThis == null ? void 0 : globalThis.location;
|
|
301
|
+
if (loc && typeof loc.hostname === "string" && loc.hostname.length > 0) {
|
|
302
|
+
return loc.hostname;
|
|
303
|
+
}
|
|
304
|
+
} catch (e) {
|
|
305
|
+
}
|
|
306
|
+
return void 0;
|
|
307
|
+
}
|
|
308
|
+
function shouldAutoEnableLocalWorkshop() {
|
|
309
|
+
if (isLocalDevHost(readRuntimeHostname())) return true;
|
|
310
|
+
if (readEnvVar("NODE_ENV") === "development") return true;
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
function resolveLocalDebuggerBaseUrl(baseUrl) {
|
|
314
|
+
var _a, _b, _c;
|
|
315
|
+
if (baseUrl === null) return null;
|
|
316
|
+
if (typeof baseUrl === "string" && baseUrl.length > 0) {
|
|
317
|
+
return (_a = formatEndpoint(baseUrl)) != null ? _a : null;
|
|
318
|
+
}
|
|
319
|
+
const explicitUrlEnv = readEnvVar(LOCAL_DEBUGGER_ENV_VAR);
|
|
320
|
+
if (explicitUrlEnv) return (_b = formatEndpoint(explicitUrlEnv)) != null ? _b : null;
|
|
321
|
+
const workshopEnv = readWorkshopEnv();
|
|
322
|
+
if (workshopEnv === "disable") return null;
|
|
323
|
+
if (workshopEnv === "enable") return DEFAULT_LOCAL_WORKSHOP_URL;
|
|
324
|
+
if (workshopEnv && "url" in workshopEnv) return (_c = formatEndpoint(workshopEnv.url)) != null ? _c : null;
|
|
325
|
+
if (shouldAutoEnableLocalWorkshop()) return DEFAULT_LOCAL_WORKSHOP_URL;
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
function mirrorTraceExportToLocalDebugger(body, options = {}) {
|
|
329
|
+
var _a;
|
|
330
|
+
const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
|
|
331
|
+
if (!baseUrl) return;
|
|
332
|
+
void postJson(`${baseUrl}traces`, body, {}, {
|
|
333
|
+
maxAttempts: 1,
|
|
334
|
+
debug: (_a = options.debug) != null ? _a : false,
|
|
335
|
+
sdkName: options.sdkName
|
|
336
|
+
}).catch(() => {
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
function mirrorPartialEventToLocalDebugger(event, options = {}) {
|
|
340
|
+
var _a;
|
|
341
|
+
const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
|
|
342
|
+
if (!baseUrl) return;
|
|
343
|
+
const headers = options.writeKey ? { Authorization: `Bearer ${options.writeKey}` } : {};
|
|
344
|
+
void postJson(`${baseUrl}events/track_partial`, event, headers, {
|
|
345
|
+
maxAttempts: 1,
|
|
346
|
+
debug: (_a = options.debug) != null ? _a : false,
|
|
347
|
+
sdkName: options.sdkName
|
|
348
|
+
}).catch(() => {
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
var SHUTDOWN_DEADLINE_MS = 1e4;
|
|
352
|
+
var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
200
353
|
function mergePatches(target, source) {
|
|
201
354
|
var _a, _b, _c, _d;
|
|
202
355
|
const out = { ...target, ...source };
|
|
@@ -214,7 +367,8 @@ var EventShipper = class {
|
|
|
214
367
|
this.sticky = /* @__PURE__ */ new Map();
|
|
215
368
|
this.timers = /* @__PURE__ */ new Map();
|
|
216
369
|
this.inFlight = /* @__PURE__ */ new Set();
|
|
217
|
-
|
|
370
|
+
this.hasShutdown = false;
|
|
371
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
218
372
|
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
219
373
|
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
220
374
|
this.enabled = opts.enabled !== false;
|
|
@@ -223,11 +377,16 @@ var EventShipper = class {
|
|
|
223
377
|
this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
|
|
224
378
|
this.prefix = `[raindrop-ai/${this.sdkName}]`;
|
|
225
379
|
this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
|
|
380
|
+
this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
|
|
381
|
+
this.localDebuggerUrl = (_f = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _f : void 0;
|
|
382
|
+
if (this.debug && this.localDebuggerUrl) {
|
|
383
|
+
console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
|
|
384
|
+
}
|
|
226
385
|
const isNode = typeof process !== "undefined" && typeof process.version === "string";
|
|
227
386
|
this.context = {
|
|
228
387
|
library: {
|
|
229
|
-
name: (
|
|
230
|
-
version: (
|
|
388
|
+
name: (_g = opts.libraryName) != null ? _g : "@raindrop-ai/core",
|
|
389
|
+
version: (_h = opts.libraryVersion) != null ? _h : "0.0.0"
|
|
231
390
|
},
|
|
232
391
|
metadata: {
|
|
233
392
|
jsRuntime: isNode ? "node" : "web",
|
|
@@ -241,10 +400,52 @@ var EventShipper = class {
|
|
|
241
400
|
authHeaders() {
|
|
242
401
|
return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
|
|
243
402
|
}
|
|
403
|
+
/**
|
|
404
|
+
* Build the retry/timeout options for one POST, honoring the shutdown
|
|
405
|
+
* deadline. Returns `null` when the shutdown drain window is exhausted —
|
|
406
|
+
* the caller must drop the payload (with a rate-limited warning) instead
|
|
407
|
+
* of issuing a request that could outlive process exit.
|
|
408
|
+
*
|
|
409
|
+
* Checked fresh on EVERY send, so a shutdown that begins while the flush
|
|
410
|
+
* path is mid-drain takes effect immediately: no further retries, and the
|
|
411
|
+
* per-attempt timeout is clamped to the remaining window. After
|
|
412
|
+
* `shutdown()` returns (deadline cleared, `hasShutdown` still set),
|
|
413
|
+
* sends — late callers, or flush work the deadline abandoned mid-drain —
|
|
414
|
+
* run as a single short attempt rather than regaining the full retry
|
|
415
|
+
* schedule.
|
|
416
|
+
*/
|
|
417
|
+
requestOpts() {
|
|
418
|
+
if (this.shutdownDeadlineAt !== void 0) {
|
|
419
|
+
const remainingMs = this.shutdownDeadlineAt - Date.now();
|
|
420
|
+
if (remainingMs <= 0) return null;
|
|
421
|
+
return {
|
|
422
|
+
maxAttempts: 1,
|
|
423
|
+
debug: this.debug,
|
|
424
|
+
sdkName: this.sdkName,
|
|
425
|
+
timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
if (this.hasShutdown) {
|
|
429
|
+
return {
|
|
430
|
+
maxAttempts: 1,
|
|
431
|
+
debug: this.debug,
|
|
432
|
+
sdkName: this.sdkName,
|
|
433
|
+
timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
|
|
437
|
+
}
|
|
244
438
|
async patch(eventId, patch) {
|
|
245
439
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
246
440
|
if (!this.enabled) return;
|
|
247
441
|
if (!eventId || !eventId.trim()) return;
|
|
442
|
+
const maxChars = resolveMaxTextFieldChars(this.maxTextFieldCharsOpt);
|
|
443
|
+
if (typeof patch.input === "string" && patch.input.length > maxChars) {
|
|
444
|
+
patch = { ...patch, input: capText(patch.input, maxChars) };
|
|
445
|
+
}
|
|
446
|
+
if (typeof patch.output === "string" && patch.output.length > maxChars) {
|
|
447
|
+
patch = { ...patch, output: capText(patch.output, maxChars) };
|
|
448
|
+
}
|
|
248
449
|
if (this.debug) {
|
|
249
450
|
console.log(`${this.prefix} queue patch`, {
|
|
250
451
|
eventId,
|
|
@@ -291,9 +492,16 @@ var EventShipper = class {
|
|
|
291
492
|
})));
|
|
292
493
|
}
|
|
293
494
|
async shutdown() {
|
|
294
|
-
|
|
295
|
-
this.
|
|
296
|
-
|
|
495
|
+
this.hasShutdown = true;
|
|
496
|
+
this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
|
|
497
|
+
try {
|
|
498
|
+
for (const t of this.timers.values()) clearTimeout(t);
|
|
499
|
+
this.timers.clear();
|
|
500
|
+
const settled = await raceWithTimeout(this.flush(), SHUTDOWN_DEADLINE_MS);
|
|
501
|
+
if (!settled) this.warnShutdownDrop("in-flight request(s) at shutdown");
|
|
502
|
+
} finally {
|
|
503
|
+
this.shutdownDeadlineAt = void 0;
|
|
504
|
+
}
|
|
297
505
|
}
|
|
298
506
|
async trackSignal(signal) {
|
|
299
507
|
var _a, _b;
|
|
@@ -313,16 +521,21 @@ var EventShipper = class {
|
|
|
313
521
|
}
|
|
314
522
|
}
|
|
315
523
|
];
|
|
524
|
+
if (!this.writeKey) return;
|
|
316
525
|
const url = `${this.baseUrl}signals/track`;
|
|
526
|
+
const opts = this.requestOpts();
|
|
527
|
+
if (!opts) {
|
|
528
|
+
this.warnShutdownDrop("signal");
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
317
531
|
try {
|
|
318
|
-
await postJson(url, body, this.authHeaders(),
|
|
319
|
-
maxAttempts: 3,
|
|
320
|
-
debug: this.debug,
|
|
321
|
-
sdkName: this.sdkName
|
|
322
|
-
});
|
|
532
|
+
await postJson(url, body, this.authHeaders(), opts);
|
|
323
533
|
} catch (err) {
|
|
324
534
|
const msg = err instanceof Error ? err.message : String(err);
|
|
325
|
-
|
|
535
|
+
rateLimitedLog(
|
|
536
|
+
`${this.prefix}.send_signal_failed`,
|
|
537
|
+
() => console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`)
|
|
538
|
+
);
|
|
326
539
|
}
|
|
327
540
|
}
|
|
328
541
|
async identify(users) {
|
|
@@ -343,19 +556,30 @@ var EventShipper = class {
|
|
|
343
556
|
traits: (_a = user.traits) != null ? _a : {}
|
|
344
557
|
};
|
|
345
558
|
});
|
|
559
|
+
if (!this.writeKey) return;
|
|
346
560
|
if (body.length === 0) return;
|
|
347
561
|
const url = `${this.baseUrl}users/identify`;
|
|
562
|
+
const opts = this.requestOpts();
|
|
563
|
+
if (!opts) {
|
|
564
|
+
this.warnShutdownDrop("identify");
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
348
567
|
try {
|
|
349
|
-
await postJson(url, body, this.authHeaders(),
|
|
350
|
-
maxAttempts: 3,
|
|
351
|
-
debug: this.debug,
|
|
352
|
-
sdkName: this.sdkName
|
|
353
|
-
});
|
|
568
|
+
await postJson(url, body, this.authHeaders(), opts);
|
|
354
569
|
} catch (err) {
|
|
355
570
|
const msg = err instanceof Error ? err.message : String(err);
|
|
356
|
-
|
|
571
|
+
rateLimitedLog(
|
|
572
|
+
`${this.prefix}.send_identify_failed`,
|
|
573
|
+
() => console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`)
|
|
574
|
+
);
|
|
357
575
|
}
|
|
358
576
|
}
|
|
577
|
+
warnShutdownDrop(what) {
|
|
578
|
+
rateLimitedLog(
|
|
579
|
+
`${this.prefix}.shutdown_deadline`,
|
|
580
|
+
() => console.warn(`${this.prefix} shutdown flush deadline exceeded; dropping ${what}`)
|
|
581
|
+
);
|
|
582
|
+
}
|
|
359
583
|
async flushOne(eventId) {
|
|
360
584
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
361
585
|
if (!this.enabled) return;
|
|
@@ -419,11 +643,25 @@ var EventShipper = class {
|
|
|
419
643
|
endpoint: url
|
|
420
644
|
});
|
|
421
645
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
646
|
+
if (this.localDebuggerUrl) {
|
|
647
|
+
mirrorPartialEventToLocalDebugger(payload, {
|
|
648
|
+
baseUrl: this.localDebuggerUrl,
|
|
649
|
+
writeKey: this.writeKey,
|
|
650
|
+
debug: this.debug,
|
|
651
|
+
sdkName: this.sdkName
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
if (!this.writeKey) {
|
|
655
|
+
if (!isPending) this.sticky.delete(eventId);
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
const opts = this.requestOpts();
|
|
659
|
+
if (!opts) {
|
|
660
|
+
this.warnShutdownDrop(`track_partial ${eventId}`);
|
|
661
|
+
if (!isPending) this.sticky.delete(eventId);
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
const p = postJson(url, payload, this.authHeaders(), opts);
|
|
427
665
|
this.inFlight.add(p);
|
|
428
666
|
try {
|
|
429
667
|
try {
|
|
@@ -433,7 +671,10 @@ var EventShipper = class {
|
|
|
433
671
|
}
|
|
434
672
|
} catch (err) {
|
|
435
673
|
const msg = err instanceof Error ? err.message : String(err);
|
|
436
|
-
|
|
674
|
+
rateLimitedLog(
|
|
675
|
+
`${this.prefix}.send_track_partial_failed`,
|
|
676
|
+
() => console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`)
|
|
677
|
+
);
|
|
437
678
|
}
|
|
438
679
|
} finally {
|
|
439
680
|
this.inFlight.delete(p);
|
|
@@ -443,28 +684,114 @@ var EventShipper = class {
|
|
|
443
684
|
}
|
|
444
685
|
}
|
|
445
686
|
};
|
|
446
|
-
var
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
687
|
+
var DEFAULT_SECRET_KEY_NAMES = [
|
|
688
|
+
"apikey",
|
|
689
|
+
"apisecret",
|
|
690
|
+
"apitoken",
|
|
691
|
+
"secretaccesskey",
|
|
692
|
+
"sessiontoken",
|
|
693
|
+
"privatekey",
|
|
694
|
+
"privatekeyid",
|
|
695
|
+
"clientsecret",
|
|
696
|
+
"accesstoken",
|
|
697
|
+
"refreshtoken",
|
|
698
|
+
"oauthtoken",
|
|
699
|
+
"bearertoken",
|
|
700
|
+
"authorization",
|
|
701
|
+
"password",
|
|
702
|
+
"passphrase"
|
|
703
|
+
];
|
|
704
|
+
var REDACTED_PLACEHOLDER = "[REDACTED]";
|
|
705
|
+
function normalizeKeyName(name) {
|
|
706
|
+
return name.toLowerCase().replace(/[-_.]/g, "");
|
|
451
707
|
}
|
|
452
|
-
function
|
|
453
|
-
var _a;
|
|
454
|
-
const
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
708
|
+
function redactSecretsInObject(value, options) {
|
|
709
|
+
var _a, _b;
|
|
710
|
+
const normalizedSecretSet = buildSecretSet((_a = options == null ? void 0 : options.secretKeyNames) != null ? _a : DEFAULT_SECRET_KEY_NAMES);
|
|
711
|
+
const placeholder = (_b = options == null ? void 0 : options.placeholder) != null ? _b : REDACTED_PLACEHOLDER;
|
|
712
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
713
|
+
const walk = (node) => {
|
|
714
|
+
if (node === null || typeof node !== "object") return node;
|
|
715
|
+
if (seen.has(node)) return "[CIRCULAR]";
|
|
716
|
+
seen.add(node);
|
|
717
|
+
if (Array.isArray(node)) {
|
|
718
|
+
return node.map((item) => walk(item));
|
|
719
|
+
}
|
|
720
|
+
const out = {};
|
|
721
|
+
for (const [k, v] of Object.entries(node)) {
|
|
722
|
+
if (normalizedSecretSet.has(normalizeKeyName(k))) {
|
|
723
|
+
out[k] = placeholder;
|
|
724
|
+
} else {
|
|
725
|
+
out[k] = walk(v);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return out;
|
|
729
|
+
};
|
|
730
|
+
return walk(value);
|
|
731
|
+
}
|
|
732
|
+
function buildSecretSet(names) {
|
|
733
|
+
const set = /* @__PURE__ */ new Set();
|
|
734
|
+
for (const name of names) set.add(normalizeKeyName(name));
|
|
735
|
+
return set;
|
|
736
|
+
}
|
|
737
|
+
var DEFAULT_REDACT_ATTRIBUTE_KEYS = [
|
|
738
|
+
"ai.request.providerOptions",
|
|
739
|
+
"ai.response.providerMetadata"
|
|
740
|
+
];
|
|
741
|
+
function defaultTransformSpan(span) {
|
|
742
|
+
const attrs = span.attributes;
|
|
743
|
+
if (!attrs || attrs.length === 0) return span;
|
|
744
|
+
let nextAttrs;
|
|
745
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
746
|
+
const attr = attrs[i];
|
|
747
|
+
const redacted = redactJsonAttributeValue(attr.key, attr.value);
|
|
748
|
+
if (redacted === void 0) continue;
|
|
749
|
+
if (!nextAttrs) nextAttrs = attrs.slice();
|
|
750
|
+
nextAttrs[i] = { key: attr.key, value: redacted };
|
|
751
|
+
}
|
|
752
|
+
if (!nextAttrs) return span;
|
|
753
|
+
return { ...span, attributes: nextAttrs };
|
|
754
|
+
}
|
|
755
|
+
var REDACT_JSON_ATTRIBUTE_KEYS = new Set(DEFAULT_REDACT_ATTRIBUTE_KEYS);
|
|
756
|
+
function redactJsonAttributeValue(key, value) {
|
|
757
|
+
if (!REDACT_JSON_ATTRIBUTE_KEYS.has(key)) return void 0;
|
|
758
|
+
const json = value.stringValue;
|
|
759
|
+
if (typeof json !== "string" || json.length === 0) return void 0;
|
|
760
|
+
let parsed;
|
|
761
|
+
try {
|
|
762
|
+
parsed = JSON.parse(json);
|
|
763
|
+
} catch (e) {
|
|
764
|
+
return void 0;
|
|
765
|
+
}
|
|
766
|
+
const scrubbed = redactSecretsInObject(parsed);
|
|
767
|
+
let scrubbedJson;
|
|
768
|
+
try {
|
|
769
|
+
scrubbedJson = JSON.stringify(scrubbed);
|
|
770
|
+
} catch (e) {
|
|
771
|
+
return void 0;
|
|
772
|
+
}
|
|
773
|
+
if (scrubbedJson === json) return void 0;
|
|
774
|
+
return { stringValue: scrubbedJson };
|
|
775
|
+
}
|
|
776
|
+
function applyOtelSpanAttributeLimit(limit) {
|
|
777
|
+
var _a, _b;
|
|
778
|
+
try {
|
|
779
|
+
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;
|
|
780
|
+
if (!raw) return limit;
|
|
781
|
+
const parsed = Number.parseInt(raw, 10);
|
|
782
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
783
|
+
return Math.min(limit, parsed);
|
|
784
|
+
}
|
|
785
|
+
} catch (e) {
|
|
786
|
+
}
|
|
787
|
+
return limit;
|
|
462
788
|
}
|
|
463
789
|
var TraceShipper = class {
|
|
464
790
|
constructor(opts) {
|
|
465
791
|
this.queue = [];
|
|
466
792
|
this.inFlight = /* @__PURE__ */ new Set();
|
|
467
|
-
|
|
793
|
+
this.hasShutdown = false;
|
|
794
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
468
795
|
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
469
796
|
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
470
797
|
this.enabled = opts.enabled !== false;
|
|
@@ -477,13 +804,79 @@ var TraceShipper = class {
|
|
|
477
804
|
this.prefix = `[raindrop-ai/${this.sdkName}]`;
|
|
478
805
|
this.serviceName = (_g = opts.serviceName) != null ? _g : "raindrop.core";
|
|
479
806
|
this.serviceVersion = (_h = opts.serviceVersion) != null ? _h : "0.0.0";
|
|
480
|
-
|
|
481
|
-
if (
|
|
482
|
-
this.
|
|
483
|
-
|
|
484
|
-
|
|
807
|
+
this.localDebuggerUrl = (_i = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _i : void 0;
|
|
808
|
+
if (this.debug && this.localDebuggerUrl) {
|
|
809
|
+
console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
|
|
810
|
+
}
|
|
811
|
+
this.transformSpanHook = opts.transformSpan;
|
|
812
|
+
this.disableDefaultRedaction = opts.disableDefaultRedaction === true;
|
|
813
|
+
this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Cap every string attribute value on the span. O(#attributes) length
|
|
817
|
+
* checks; only oversized values pay a slice. Runs AFTER the redaction
|
|
818
|
+
* pipeline so the default secret-scrub still sees parseable JSON in
|
|
819
|
+
* `ai.request.providerOptions` / `ai.response.providerMetadata` (capping
|
|
820
|
+
* first could cut a JSON blob mid-way, fail the parse, and ship secrets
|
|
821
|
+
* in the surviving prefix).
|
|
822
|
+
*
|
|
823
|
+
* A stricter `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var is honored
|
|
824
|
+
* for span content, matching the Python SDK and the OTel SDK convention.
|
|
825
|
+
*/
|
|
826
|
+
capSpanAttributes(span) {
|
|
827
|
+
var _a;
|
|
828
|
+
const maxChars = applyOtelSpanAttributeLimit(
|
|
829
|
+
resolveMaxTextFieldChars(this.maxTextFieldCharsOpt)
|
|
830
|
+
);
|
|
831
|
+
const attrs = span.attributes;
|
|
832
|
+
if (!attrs || attrs.length === 0) return span;
|
|
833
|
+
let nextAttrs;
|
|
834
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
835
|
+
const attr = attrs[i];
|
|
836
|
+
const value = (_a = attr.value) == null ? void 0 : _a.stringValue;
|
|
837
|
+
if (typeof value !== "string" || value.length <= maxChars) continue;
|
|
838
|
+
if (!nextAttrs) nextAttrs = attrs.slice();
|
|
839
|
+
nextAttrs[i] = {
|
|
840
|
+
key: attr.key,
|
|
841
|
+
value: { ...attr.value, stringValue: capText(value, maxChars) }
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
if (!nextAttrs) return span;
|
|
845
|
+
return { ...span, attributes: nextAttrs };
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* Apply the user `transformSpan` hook (if any) followed by the default
|
|
849
|
+
* redactor (unless disabled). Returns either the (possibly new) span to
|
|
850
|
+
* ship, or `null` to drop the span entirely.
|
|
851
|
+
*
|
|
852
|
+
* Ordering: user hook runs first so callers can rewrite the span freely
|
|
853
|
+
* (rename attrs, add new ones, scrub things the default doesn't know
|
|
854
|
+
* about). The default redactor then runs on whatever the user produced,
|
|
855
|
+
* acting as the always-on floor for documented BYOK secrets. If the user
|
|
856
|
+
* sets `disableDefaultRedaction: true`, the floor is skipped.
|
|
857
|
+
*
|
|
858
|
+
* Fail-closed: if the user hook throws, the span is dropped — a buggy
|
|
859
|
+
* hook can never accidentally ship raw, un-redacted spans.
|
|
860
|
+
*/
|
|
861
|
+
redactSpan(span) {
|
|
862
|
+
let current = span;
|
|
863
|
+
if (this.transformSpanHook) {
|
|
864
|
+
try {
|
|
865
|
+
const result = this.transformSpanHook(current);
|
|
866
|
+
if (result === null) return null;
|
|
867
|
+
if (result !== void 0) current = result;
|
|
868
|
+
} catch (err) {
|
|
869
|
+
if (this.debug) {
|
|
870
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
871
|
+
console.warn(`${this.prefix} transformSpan hook threw: ${msg}`);
|
|
872
|
+
}
|
|
873
|
+
return null;
|
|
485
874
|
}
|
|
486
875
|
}
|
|
876
|
+
if (!this.disableDefaultRedaction) {
|
|
877
|
+
current = defaultTransformSpan(current);
|
|
878
|
+
}
|
|
879
|
+
return this.capSpanAttributes(current);
|
|
487
880
|
}
|
|
488
881
|
isDebugEnabled() {
|
|
489
882
|
return this.debug;
|
|
@@ -501,8 +894,8 @@ var TraceShipper = class {
|
|
|
501
894
|
];
|
|
502
895
|
if ((_b = args2.attributes) == null ? void 0 : _b.length) attrs.push(...args2.attributes);
|
|
503
896
|
const span = { ids, name: args2.name, startTimeUnixNano: started, attributes: attrs };
|
|
504
|
-
|
|
505
|
-
|
|
897
|
+
this.mirrorToLocalDebugger(
|
|
898
|
+
buildOtlpSpan({
|
|
506
899
|
ids: span.ids,
|
|
507
900
|
name: span.name,
|
|
508
901
|
startTimeUnixNano: span.startTimeUnixNano,
|
|
@@ -510,16 +903,21 @@ var TraceShipper = class {
|
|
|
510
903
|
// placeholder — will be updated on endSpan
|
|
511
904
|
attributes: span.attributes,
|
|
512
905
|
status: { code: SpanStatusCode.UNSET }
|
|
513
|
-
})
|
|
514
|
-
|
|
515
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
516
|
-
baseUrl: this.localDebuggerUrl,
|
|
517
|
-
debug: false,
|
|
518
|
-
sdkName: this.sdkName
|
|
519
|
-
});
|
|
520
|
-
}
|
|
906
|
+
})
|
|
907
|
+
);
|
|
521
908
|
return span;
|
|
522
909
|
}
|
|
910
|
+
mirrorToLocalDebugger(span) {
|
|
911
|
+
if (!this.localDebuggerUrl) return;
|
|
912
|
+
const redacted = this.redactSpan(span);
|
|
913
|
+
if (redacted === null) return;
|
|
914
|
+
const body = buildExportTraceServiceRequest([redacted], this.serviceName, this.serviceVersion);
|
|
915
|
+
mirrorTraceExportToLocalDebugger(body, {
|
|
916
|
+
baseUrl: this.localDebuggerUrl,
|
|
917
|
+
debug: false,
|
|
918
|
+
sdkName: this.sdkName
|
|
919
|
+
});
|
|
920
|
+
}
|
|
523
921
|
endSpan(span, extra) {
|
|
524
922
|
var _a, _b;
|
|
525
923
|
if (span.endTimeUnixNano) return;
|
|
@@ -541,14 +939,7 @@ var TraceShipper = class {
|
|
|
541
939
|
status
|
|
542
940
|
});
|
|
543
941
|
this.enqueue(otlp);
|
|
544
|
-
|
|
545
|
-
const body = buildExportTraceServiceRequest([otlp], this.serviceName, this.serviceVersion);
|
|
546
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
547
|
-
baseUrl: this.localDebuggerUrl,
|
|
548
|
-
debug: false,
|
|
549
|
-
sdkName: this.sdkName
|
|
550
|
-
});
|
|
551
|
-
}
|
|
942
|
+
this.mirrorToLocalDebugger(otlp);
|
|
552
943
|
}
|
|
553
944
|
createSpan(args2) {
|
|
554
945
|
var _a;
|
|
@@ -566,14 +957,7 @@ var TraceShipper = class {
|
|
|
566
957
|
status: args2.status
|
|
567
958
|
});
|
|
568
959
|
this.enqueue(otlp);
|
|
569
|
-
|
|
570
|
-
const body = buildExportTraceServiceRequest([otlp], this.serviceName, this.serviceVersion);
|
|
571
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
572
|
-
baseUrl: this.localDebuggerUrl,
|
|
573
|
-
debug: false,
|
|
574
|
-
sdkName: this.sdkName
|
|
575
|
-
});
|
|
576
|
-
}
|
|
960
|
+
this.mirrorToLocalDebugger(otlp);
|
|
577
961
|
}
|
|
578
962
|
enqueue(span) {
|
|
579
963
|
if (!this.enabled) return;
|
|
@@ -585,10 +969,12 @@ var TraceShipper = class {
|
|
|
585
969
|
)}`
|
|
586
970
|
);
|
|
587
971
|
}
|
|
972
|
+
const redacted = this.redactSpan(span);
|
|
973
|
+
if (redacted === null) return;
|
|
588
974
|
if (this.queue.length >= this.maxQueueSize) {
|
|
589
975
|
this.queue.shift();
|
|
590
976
|
}
|
|
591
|
-
this.queue.push(
|
|
977
|
+
this.queue.push(redacted);
|
|
592
978
|
if (this.queue.length >= this.maxBatchSize) {
|
|
593
979
|
void this.flush().catch(() => {
|
|
594
980
|
});
|
|
@@ -610,6 +996,17 @@ var TraceShipper = class {
|
|
|
610
996
|
}
|
|
611
997
|
while (this.queue.length > 0) {
|
|
612
998
|
const batch = this.queue.splice(0, this.maxBatchSize);
|
|
999
|
+
if (!this.writeKey) continue;
|
|
1000
|
+
const opts = this.requestOpts();
|
|
1001
|
+
if (!opts) {
|
|
1002
|
+
rateLimitedLog(
|
|
1003
|
+
`${this.prefix}.shutdown_deadline`,
|
|
1004
|
+
() => console.warn(
|
|
1005
|
+
`${this.prefix} shutdown flush deadline exceeded; dropping ${batch.length} spans`
|
|
1006
|
+
)
|
|
1007
|
+
);
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
613
1010
|
const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
|
|
614
1011
|
const url = `${this.baseUrl}traces`;
|
|
615
1012
|
if (this.debug) {
|
|
@@ -618,11 +1015,7 @@ var TraceShipper = class {
|
|
|
618
1015
|
endpoint: url
|
|
619
1016
|
});
|
|
620
1017
|
}
|
|
621
|
-
const p = postJson(url, body, this.authHeaders(),
|
|
622
|
-
maxAttempts: 3,
|
|
623
|
-
debug: this.debug,
|
|
624
|
-
sdkName: this.sdkName
|
|
625
|
-
});
|
|
1018
|
+
const p = postJson(url, body, this.authHeaders(), opts);
|
|
626
1019
|
this.inFlight.add(p);
|
|
627
1020
|
try {
|
|
628
1021
|
try {
|
|
@@ -630,21 +1023,61 @@ var TraceShipper = class {
|
|
|
630
1023
|
if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
|
|
631
1024
|
} catch (err) {
|
|
632
1025
|
const msg = err instanceof Error ? err.message : String(err);
|
|
633
|
-
|
|
1026
|
+
rateLimitedLog(
|
|
1027
|
+
`${this.prefix}.send_spans_failed`,
|
|
1028
|
+
() => console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`)
|
|
1029
|
+
);
|
|
634
1030
|
}
|
|
635
1031
|
} finally {
|
|
636
1032
|
this.inFlight.delete(p);
|
|
637
1033
|
}
|
|
638
1034
|
}
|
|
639
1035
|
}
|
|
1036
|
+
/** See EventShipper.requestOpts — same shutdown-budget semantics. */
|
|
1037
|
+
requestOpts() {
|
|
1038
|
+
if (this.shutdownDeadlineAt !== void 0) {
|
|
1039
|
+
const remainingMs = this.shutdownDeadlineAt - Date.now();
|
|
1040
|
+
if (remainingMs <= 0) return null;
|
|
1041
|
+
return {
|
|
1042
|
+
maxAttempts: 1,
|
|
1043
|
+
debug: this.debug,
|
|
1044
|
+
sdkName: this.sdkName,
|
|
1045
|
+
timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
if (this.hasShutdown) {
|
|
1049
|
+
return {
|
|
1050
|
+
maxAttempts: 1,
|
|
1051
|
+
debug: this.debug,
|
|
1052
|
+
sdkName: this.sdkName,
|
|
1053
|
+
timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
|
|
1057
|
+
}
|
|
640
1058
|
async shutdown() {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
1059
|
+
this.hasShutdown = true;
|
|
1060
|
+
this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
|
|
1061
|
+
try {
|
|
1062
|
+
if (this.timer) {
|
|
1063
|
+
clearTimeout(this.timer);
|
|
1064
|
+
this.timer = void 0;
|
|
1065
|
+
}
|
|
1066
|
+
const drain = async () => {
|
|
1067
|
+
await this.flush();
|
|
1068
|
+
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
1069
|
+
})));
|
|
1070
|
+
};
|
|
1071
|
+
const settled = await raceWithTimeout(drain(), SHUTDOWN_DEADLINE_MS);
|
|
1072
|
+
if (!settled) {
|
|
1073
|
+
rateLimitedLog(
|
|
1074
|
+
`${this.prefix}.shutdown_deadline`,
|
|
1075
|
+
() => console.warn(`${this.prefix} shutdown flush deadline exceeded; abandoning in-flight spans`)
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
} finally {
|
|
1079
|
+
this.shutdownDeadlineAt = void 0;
|
|
644
1080
|
}
|
|
645
|
-
await this.flush();
|
|
646
|
-
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
647
|
-
})));
|
|
648
1081
|
}
|
|
649
1082
|
};
|
|
650
1083
|
|
|
@@ -909,18 +1342,85 @@ function transcriptToProperties(summary) {
|
|
|
909
1342
|
|
|
910
1343
|
// src/event-mapper.ts
|
|
911
1344
|
var MAX_ATTR_LENGTH = 32768;
|
|
1345
|
+
var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
|
|
1346
|
+
var MAX_TEXT_FIELD_CHARS = 1e6;
|
|
1347
|
+
var MAX_BOUNDED_DEPTH = 12;
|
|
912
1348
|
function truncate(value) {
|
|
913
1349
|
if (value === void 0) return void 0;
|
|
914
1350
|
if (value.length <= MAX_ATTR_LENGTH) return value;
|
|
915
1351
|
const suffix = "\n...[truncated]";
|
|
916
1352
|
return value.slice(0, MAX_ATTR_LENGTH - suffix.length) + suffix;
|
|
917
1353
|
}
|
|
1354
|
+
function capText2(value, limit = MAX_TEXT_FIELD_CHARS) {
|
|
1355
|
+
if (value.length <= limit) return value;
|
|
1356
|
+
if (limit > TRUNCATION_MARKER2.length) {
|
|
1357
|
+
return value.slice(0, limit - TRUNCATION_MARKER2.length) + TRUNCATION_MARKER2;
|
|
1358
|
+
}
|
|
1359
|
+
return value.slice(0, limit);
|
|
1360
|
+
}
|
|
1361
|
+
function boundedClone2(value, budget, depth) {
|
|
1362
|
+
if (budget.remaining <= 0) return TRUNCATION_MARKER2;
|
|
1363
|
+
if (typeof value === "string") {
|
|
1364
|
+
if (value.length > budget.remaining) {
|
|
1365
|
+
const taken = value.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
|
|
1366
|
+
budget.remaining = 0;
|
|
1367
|
+
return taken;
|
|
1368
|
+
}
|
|
1369
|
+
budget.remaining -= Math.max(value.length, 1);
|
|
1370
|
+
return value;
|
|
1371
|
+
}
|
|
1372
|
+
if (value === null || typeof value === "number" || typeof value === "boolean") {
|
|
1373
|
+
budget.remaining -= 8;
|
|
1374
|
+
return value;
|
|
1375
|
+
}
|
|
1376
|
+
if (typeof value !== "object") {
|
|
1377
|
+
budget.remaining -= 8;
|
|
1378
|
+
return value;
|
|
1379
|
+
}
|
|
1380
|
+
if (depth >= MAX_BOUNDED_DEPTH) {
|
|
1381
|
+
budget.remaining -= 16;
|
|
1382
|
+
return `<max depth: ${TRUNCATION_MARKER2}>`;
|
|
1383
|
+
}
|
|
1384
|
+
const withToJson = value;
|
|
1385
|
+
if (typeof withToJson.toJSON === "function") {
|
|
1386
|
+
try {
|
|
1387
|
+
return boundedClone2(withToJson.toJSON(), budget, depth + 1);
|
|
1388
|
+
} catch (e) {
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
if (Array.isArray(value)) {
|
|
1392
|
+
const out2 = [];
|
|
1393
|
+
for (const item of value) {
|
|
1394
|
+
if (budget.remaining <= 0) {
|
|
1395
|
+
out2.push(TRUNCATION_MARKER2);
|
|
1396
|
+
break;
|
|
1397
|
+
}
|
|
1398
|
+
out2.push(boundedClone2(item, budget, depth + 1));
|
|
1399
|
+
}
|
|
1400
|
+
return out2;
|
|
1401
|
+
}
|
|
1402
|
+
const out = {};
|
|
1403
|
+
for (const key of Object.keys(value)) {
|
|
1404
|
+
if (budget.remaining <= 0) {
|
|
1405
|
+
out["..."] = TRUNCATION_MARKER2;
|
|
1406
|
+
break;
|
|
1407
|
+
}
|
|
1408
|
+
budget.remaining -= Math.max(key.length, 1);
|
|
1409
|
+
out[key] = boundedClone2(value[key], budget, depth + 1);
|
|
1410
|
+
}
|
|
1411
|
+
return out;
|
|
1412
|
+
}
|
|
918
1413
|
function safeStringify(value) {
|
|
919
1414
|
if (value === void 0 || value === null) return void 0;
|
|
920
1415
|
try {
|
|
921
|
-
|
|
1416
|
+
const pruned = boundedClone2(
|
|
1417
|
+
value,
|
|
1418
|
+
{ remaining: MAX_ATTR_LENGTH + TRUNCATION_MARKER2.length + 256 },
|
|
1419
|
+
0
|
|
1420
|
+
);
|
|
1421
|
+
return truncate(JSON.stringify(pruned));
|
|
922
1422
|
} catch (e) {
|
|
923
|
-
return truncate(String(value));
|
|
1423
|
+
return truncate(capText2(String(value), MAX_ATTR_LENGTH + 16));
|
|
924
1424
|
}
|
|
925
1425
|
}
|
|
926
1426
|
var STATE_DIR = join(tmpdir(), "raindrop-claude-code");
|
|
@@ -1101,7 +1601,9 @@ async function handleUserPromptSubmit(payload, convoId, config, properties, even
|
|
|
1101
1601
|
userId: config.userId,
|
|
1102
1602
|
convoId,
|
|
1103
1603
|
eventName: config.eventName,
|
|
1104
|
-
|
|
1604
|
+
// Capped so a pasted multi-MB prompt can't produce an event that the
|
|
1605
|
+
// ingest API rejects (silent data loss) — it lands truncated instead.
|
|
1606
|
+
input: payload.prompt !== void 0 ? capText2(payload.prompt) : void 0,
|
|
1105
1607
|
model,
|
|
1106
1608
|
properties
|
|
1107
1609
|
};
|
|
@@ -1224,7 +1726,7 @@ async function handlePostCompact(payload, eventId, convoId, config, properties,
|
|
|
1224
1726
|
properties: {
|
|
1225
1727
|
...properties,
|
|
1226
1728
|
compaction_trigger: payload.trigger,
|
|
1227
|
-
compact_summary: payload.compact_summary
|
|
1729
|
+
compact_summary: payload.compact_summary !== void 0 ? capText2(payload.compact_summary) : void 0
|
|
1228
1730
|
}
|
|
1229
1731
|
});
|
|
1230
1732
|
}
|
|
@@ -1459,7 +1961,7 @@ function enrichFromTranscript(payload, eventId, traceShipper) {
|
|
|
1459
1961
|
attrInt("gen_ai.usage.prompt_tokens", phase.inputTokens),
|
|
1460
1962
|
attrInt("gen_ai.usage.completion_tokens", phase.outputTokens),
|
|
1461
1963
|
...phase.cacheReadTokens > 0 ? [attrInt("gen_ai.usage.cache_read_tokens", phase.cacheReadTokens)] : [],
|
|
1462
|
-
attrString("ai.response.text", outputText),
|
|
1964
|
+
attrString("ai.response.text", truncate(outputText)),
|
|
1463
1965
|
...toolCallNames ? [attrString("ai.toolCall.names", toolCallNames)] : [],
|
|
1464
1966
|
...phase.hasThinking ? [attrString("ai.has_thinking", "true")] : [],
|
|
1465
1967
|
attrInt("ai.llm_call.index", emittedPhaseIndex)
|
|
@@ -1519,7 +2021,7 @@ async function handleStopOrFailure(payload, eventId, convoId, config, properties
|
|
|
1519
2021
|
userId: config.userId,
|
|
1520
2022
|
convoId,
|
|
1521
2023
|
eventName: config.eventName,
|
|
1522
|
-
output,
|
|
2024
|
+
output: output !== void 0 ? capText2(output) : void 0,
|
|
1523
2025
|
...(summary == null ? void 0 : summary.model) ? { model: summary.model } : {},
|
|
1524
2026
|
properties: {
|
|
1525
2027
|
...properties,
|
|
@@ -1627,6 +2129,7 @@ import { join as join3 } from "path";
|
|
|
1627
2129
|
var DEFAULT_PORT = 5899;
|
|
1628
2130
|
var DEFAULT_URL = `http://localhost:${DEFAULT_PORT}/v1/`;
|
|
1629
2131
|
var HEALTH_TIMEOUT_MS = 300;
|
|
2132
|
+
var MIRROR_TIMEOUT_MS = 2e3;
|
|
1630
2133
|
var CACHE_DIR = join3(tmpdir2(), "raindrop-claude-code");
|
|
1631
2134
|
var CACHE_FILE = join3(CACHE_DIR, "debugger_cache");
|
|
1632
2135
|
var CACHE_TTL_MS = 5e3;
|
|
@@ -1650,10 +2153,8 @@ function writeCache(url) {
|
|
|
1650
2153
|
async function healthPing(baseUrl) {
|
|
1651
2154
|
const healthUrl = baseUrl.replace(/\/v1\/$/, "/health");
|
|
1652
2155
|
try {
|
|
1653
|
-
const
|
|
1654
|
-
const
|
|
1655
|
-
const resp = await fetch(healthUrl, { signal: controller.signal });
|
|
1656
|
-
clearTimeout(timeout);
|
|
2156
|
+
const signal = AbortSignal.timeout(HEALTH_TIMEOUT_MS);
|
|
2157
|
+
const resp = await fetch(healthUrl, { signal });
|
|
1657
2158
|
if (resp.ok) {
|
|
1658
2159
|
const body = await resp.json();
|
|
1659
2160
|
if (body.ok) return baseUrl;
|
|
@@ -1696,7 +2197,8 @@ function mirrorEventToLocalDebugger(baseUrl, payload, debug) {
|
|
|
1696
2197
|
void fetch(url, {
|
|
1697
2198
|
method: "POST",
|
|
1698
2199
|
headers: { "Content-Type": "application/json" },
|
|
1699
|
-
body: JSON.stringify(payload)
|
|
2200
|
+
body: JSON.stringify(payload),
|
|
2201
|
+
signal: AbortSignal.timeout(MIRROR_TIMEOUT_MS)
|
|
1700
2202
|
}).catch(() => {
|
|
1701
2203
|
});
|
|
1702
2204
|
}
|
|
@@ -1707,6 +2209,35 @@ import { join as join4 } from "path";
|
|
|
1707
2209
|
import { tmpdir as tmpdir3 } from "os";
|
|
1708
2210
|
var STDIN_TIMEOUT_MS = 5e3;
|
|
1709
2211
|
var STATE_DIR2 = join4(tmpdir3(), "raindrop-claude-code");
|
|
2212
|
+
var DEFAULT_HOOK_FLUSH_DEADLINE_MS = 8e3;
|
|
2213
|
+
var MIN_FLUSH_WINDOW_MS = 250;
|
|
2214
|
+
function hookFlushDeadlineMs() {
|
|
2215
|
+
const raw = process.env["RAINDROP_HOOK_FLUSH_DEADLINE_MS"];
|
|
2216
|
+
if (raw) {
|
|
2217
|
+
const parsed = parseInt(raw, 10);
|
|
2218
|
+
if (Number.isFinite(parsed) && parsed > 0) return parsed;
|
|
2219
|
+
}
|
|
2220
|
+
return DEFAULT_HOOK_FLUSH_DEADLINE_MS;
|
|
2221
|
+
}
|
|
2222
|
+
function withDeadline(promise, ms) {
|
|
2223
|
+
return new Promise((resolve2, reject) => {
|
|
2224
|
+
const timer = setTimeout(() => {
|
|
2225
|
+
promise.catch(() => {
|
|
2226
|
+
});
|
|
2227
|
+
resolve2("deadline");
|
|
2228
|
+
}, ms);
|
|
2229
|
+
promise.then(
|
|
2230
|
+
(value) => {
|
|
2231
|
+
clearTimeout(timer);
|
|
2232
|
+
resolve2(value);
|
|
2233
|
+
},
|
|
2234
|
+
(err) => {
|
|
2235
|
+
clearTimeout(timer);
|
|
2236
|
+
reject(err);
|
|
2237
|
+
}
|
|
2238
|
+
);
|
|
2239
|
+
});
|
|
2240
|
+
}
|
|
1710
2241
|
function getOtlpTraceId(sessionId) {
|
|
1711
2242
|
try {
|
|
1712
2243
|
const key = `rootspan_${sessionId}`.replace(/[^a-zA-Z0-9_\-]/g, "_");
|
|
@@ -1744,6 +2275,7 @@ function readStdin() {
|
|
|
1744
2275
|
});
|
|
1745
2276
|
}
|
|
1746
2277
|
async function handleHook() {
|
|
2278
|
+
const startedAtMs = Date.now();
|
|
1747
2279
|
const raw = await readStdin();
|
|
1748
2280
|
if (!raw.trim()) {
|
|
1749
2281
|
return;
|
|
@@ -1819,9 +2351,23 @@ async function handleHook() {
|
|
|
1819
2351
|
};
|
|
1820
2352
|
}
|
|
1821
2353
|
try {
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
2354
|
+
const remainingMs = Math.max(
|
|
2355
|
+
MIN_FLUSH_WINDOW_MS,
|
|
2356
|
+
hookFlushDeadlineMs() - (Date.now() - startedAtMs)
|
|
2357
|
+
);
|
|
2358
|
+
const outcome = await withDeadline(
|
|
2359
|
+
(async () => {
|
|
2360
|
+
await mapHookToRaindrop(payload, mapperConfig, eventShipper, traceShipper);
|
|
2361
|
+
await Promise.all([eventShipper.flush(), traceShipper.flush()]);
|
|
2362
|
+
await Promise.all([eventShipper.shutdown(), traceShipper.shutdown()]);
|
|
2363
|
+
})(),
|
|
2364
|
+
remainingMs
|
|
2365
|
+
);
|
|
2366
|
+
if (outcome === "deadline" && config.debug) {
|
|
2367
|
+
console.error(
|
|
2368
|
+
`[raindrop-ai/claude-code] hook work exceeded the ${hookFlushDeadlineMs()}ms deadline; exiting without waiting on the network (pending telemetry dropped)`
|
|
2369
|
+
);
|
|
2370
|
+
}
|
|
1825
2371
|
} catch (err) {
|
|
1826
2372
|
if (config.debug) {
|
|
1827
2373
|
console.error(
|
|
@@ -2465,7 +3011,7 @@ async function main() {
|
|
|
2465
3011
|
}
|
|
2466
3012
|
case "hook": {
|
|
2467
3013
|
await handleHook();
|
|
2468
|
-
|
|
3014
|
+
process.exit(0);
|
|
2469
3015
|
}
|
|
2470
3016
|
case "mcp-serve": {
|
|
2471
3017
|
await startMcpServer();
|