@raindrop-ai/claude-code 0.0.13 → 0.0.15
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 +692 -109
- package/dist/index.cjs +642 -105
- package/dist/index.d.cts +181 -2
- package/dist/index.d.ts +181 -2
- package/dist/index.js +642 -105
- 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-SK6EJEO7.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,112 @@ 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 PROJECT_ID_HEADER = "X-Raindrop-Project-Id";
|
|
388
|
+
var PROJECT_ID_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
389
|
+
function isValidProjectIdSlug(value) {
|
|
390
|
+
return PROJECT_ID_SLUG_PATTERN.test(value);
|
|
391
|
+
}
|
|
392
|
+
function normalizeProjectId(raw, opts) {
|
|
393
|
+
if (typeof raw !== "string") return void 0;
|
|
394
|
+
const trimmed = raw.trim();
|
|
395
|
+
if (!trimmed) return void 0;
|
|
396
|
+
if (!isValidProjectIdSlug(trimmed) && opts.debug) {
|
|
397
|
+
console.warn(
|
|
398
|
+
`${opts.prefix} projectId "${trimmed}" does not match slug ${PROJECT_ID_SLUG_PATTERN.source}; sending anyway \u2014 backend may reject with HTTP 400`
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
return trimmed;
|
|
402
|
+
}
|
|
403
|
+
function projectIdHeaders(projectId) {
|
|
404
|
+
return projectId ? { [PROJECT_ID_HEADER]: projectId } : {};
|
|
405
|
+
}
|
|
406
|
+
var SHUTDOWN_DEADLINE_MS = 1e4;
|
|
407
|
+
var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
236
408
|
function mergePatches(target, source) {
|
|
237
409
|
var _a, _b, _c, _d;
|
|
238
410
|
const out = { ...target, ...source };
|
|
@@ -250,7 +422,8 @@ var EventShipper = class {
|
|
|
250
422
|
this.sticky = /* @__PURE__ */ new Map();
|
|
251
423
|
this.timers = /* @__PURE__ */ new Map();
|
|
252
424
|
this.inFlight = /* @__PURE__ */ new Set();
|
|
253
|
-
|
|
425
|
+
this.hasShutdown = false;
|
|
426
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
254
427
|
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
255
428
|
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
256
429
|
this.enabled = opts.enabled !== false;
|
|
@@ -259,11 +432,20 @@ var EventShipper = class {
|
|
|
259
432
|
this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
|
|
260
433
|
this.prefix = `[raindrop-ai/${this.sdkName}]`;
|
|
261
434
|
this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
|
|
435
|
+
this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
|
|
436
|
+
this.localDebuggerUrl = (_f = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _f : void 0;
|
|
437
|
+
if (this.debug && this.localDebuggerUrl) {
|
|
438
|
+
console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
|
|
439
|
+
}
|
|
440
|
+
this.projectId = normalizeProjectId(opts.projectId, {
|
|
441
|
+
debug: this.debug,
|
|
442
|
+
prefix: this.prefix
|
|
443
|
+
});
|
|
262
444
|
const isNode = typeof process !== "undefined" && typeof process.version === "string";
|
|
263
445
|
this.context = {
|
|
264
446
|
library: {
|
|
265
|
-
name: (
|
|
266
|
-
version: (
|
|
447
|
+
name: (_g = opts.libraryName) != null ? _g : "@raindrop-ai/core",
|
|
448
|
+
version: (_h = opts.libraryVersion) != null ? _h : "0.0.0"
|
|
267
449
|
},
|
|
268
450
|
metadata: {
|
|
269
451
|
jsRuntime: isNode ? "node" : "web",
|
|
@@ -277,10 +459,55 @@ var EventShipper = class {
|
|
|
277
459
|
authHeaders() {
|
|
278
460
|
return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
|
|
279
461
|
}
|
|
462
|
+
requestHeaders() {
|
|
463
|
+
return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Build the retry/timeout options for one POST, honoring the shutdown
|
|
467
|
+
* deadline. Returns `null` when the shutdown drain window is exhausted —
|
|
468
|
+
* the caller must drop the payload (with a rate-limited warning) instead
|
|
469
|
+
* of issuing a request that could outlive process exit.
|
|
470
|
+
*
|
|
471
|
+
* Checked fresh on EVERY send, so a shutdown that begins while the flush
|
|
472
|
+
* path is mid-drain takes effect immediately: no further retries, and the
|
|
473
|
+
* per-attempt timeout is clamped to the remaining window. After
|
|
474
|
+
* `shutdown()` returns (deadline cleared, `hasShutdown` still set),
|
|
475
|
+
* sends — late callers, or flush work the deadline abandoned mid-drain —
|
|
476
|
+
* run as a single short attempt rather than regaining the full retry
|
|
477
|
+
* schedule.
|
|
478
|
+
*/
|
|
479
|
+
requestOpts() {
|
|
480
|
+
if (this.shutdownDeadlineAt !== void 0) {
|
|
481
|
+
const remainingMs = this.shutdownDeadlineAt - Date.now();
|
|
482
|
+
if (remainingMs <= 0) return null;
|
|
483
|
+
return {
|
|
484
|
+
maxAttempts: 1,
|
|
485
|
+
debug: this.debug,
|
|
486
|
+
sdkName: this.sdkName,
|
|
487
|
+
timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
if (this.hasShutdown) {
|
|
491
|
+
return {
|
|
492
|
+
maxAttempts: 1,
|
|
493
|
+
debug: this.debug,
|
|
494
|
+
sdkName: this.sdkName,
|
|
495
|
+
timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
|
|
499
|
+
}
|
|
280
500
|
async patch(eventId, patch) {
|
|
281
501
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
282
502
|
if (!this.enabled) return;
|
|
283
503
|
if (!eventId || !eventId.trim()) return;
|
|
504
|
+
const maxChars = resolveMaxTextFieldChars(this.maxTextFieldCharsOpt);
|
|
505
|
+
if (typeof patch.input === "string" && patch.input.length > maxChars) {
|
|
506
|
+
patch = { ...patch, input: capText(patch.input, maxChars) };
|
|
507
|
+
}
|
|
508
|
+
if (typeof patch.output === "string" && patch.output.length > maxChars) {
|
|
509
|
+
patch = { ...patch, output: capText(patch.output, maxChars) };
|
|
510
|
+
}
|
|
284
511
|
if (this.debug) {
|
|
285
512
|
console.log(`${this.prefix} queue patch`, {
|
|
286
513
|
eventId,
|
|
@@ -327,9 +554,16 @@ var EventShipper = class {
|
|
|
327
554
|
})));
|
|
328
555
|
}
|
|
329
556
|
async shutdown() {
|
|
330
|
-
|
|
331
|
-
this.
|
|
332
|
-
|
|
557
|
+
this.hasShutdown = true;
|
|
558
|
+
this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
|
|
559
|
+
try {
|
|
560
|
+
for (const t of this.timers.values()) clearTimeout(t);
|
|
561
|
+
this.timers.clear();
|
|
562
|
+
const settled = await raceWithTimeout(this.flush(), SHUTDOWN_DEADLINE_MS);
|
|
563
|
+
if (!settled) this.warnShutdownDrop("in-flight request(s) at shutdown");
|
|
564
|
+
} finally {
|
|
565
|
+
this.shutdownDeadlineAt = void 0;
|
|
566
|
+
}
|
|
333
567
|
}
|
|
334
568
|
async trackSignal(signal) {
|
|
335
569
|
var _a, _b;
|
|
@@ -349,16 +583,21 @@ var EventShipper = class {
|
|
|
349
583
|
}
|
|
350
584
|
}
|
|
351
585
|
];
|
|
586
|
+
if (!this.writeKey) return;
|
|
352
587
|
const url = `${this.baseUrl}signals/track`;
|
|
588
|
+
const opts = this.requestOpts();
|
|
589
|
+
if (!opts) {
|
|
590
|
+
this.warnShutdownDrop("signal");
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
353
593
|
try {
|
|
354
|
-
await postJson(url, body, this.
|
|
355
|
-
maxAttempts: 3,
|
|
356
|
-
debug: this.debug,
|
|
357
|
-
sdkName: this.sdkName
|
|
358
|
-
});
|
|
594
|
+
await postJson(url, body, this.requestHeaders(), opts);
|
|
359
595
|
} catch (err) {
|
|
360
596
|
const msg = err instanceof Error ? err.message : String(err);
|
|
361
|
-
|
|
597
|
+
rateLimitedLog(
|
|
598
|
+
`${this.prefix}.send_signal_failed`,
|
|
599
|
+
() => console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`)
|
|
600
|
+
);
|
|
362
601
|
}
|
|
363
602
|
}
|
|
364
603
|
async identify(users) {
|
|
@@ -379,19 +618,30 @@ var EventShipper = class {
|
|
|
379
618
|
traits: (_a = user.traits) != null ? _a : {}
|
|
380
619
|
};
|
|
381
620
|
});
|
|
621
|
+
if (!this.writeKey) return;
|
|
382
622
|
if (body.length === 0) return;
|
|
383
623
|
const url = `${this.baseUrl}users/identify`;
|
|
624
|
+
const opts = this.requestOpts();
|
|
625
|
+
if (!opts) {
|
|
626
|
+
this.warnShutdownDrop("identify");
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
384
629
|
try {
|
|
385
|
-
await postJson(url, body, this.
|
|
386
|
-
maxAttempts: 3,
|
|
387
|
-
debug: this.debug,
|
|
388
|
-
sdkName: this.sdkName
|
|
389
|
-
});
|
|
630
|
+
await postJson(url, body, this.requestHeaders(), opts);
|
|
390
631
|
} catch (err) {
|
|
391
632
|
const msg = err instanceof Error ? err.message : String(err);
|
|
392
|
-
|
|
633
|
+
rateLimitedLog(
|
|
634
|
+
`${this.prefix}.send_identify_failed`,
|
|
635
|
+
() => console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`)
|
|
636
|
+
);
|
|
393
637
|
}
|
|
394
638
|
}
|
|
639
|
+
warnShutdownDrop(what) {
|
|
640
|
+
rateLimitedLog(
|
|
641
|
+
`${this.prefix}.shutdown_deadline`,
|
|
642
|
+
() => console.warn(`${this.prefix} shutdown flush deadline exceeded; dropping ${what}`)
|
|
643
|
+
);
|
|
644
|
+
}
|
|
395
645
|
async flushOne(eventId) {
|
|
396
646
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
397
647
|
if (!this.enabled) return;
|
|
@@ -455,11 +705,25 @@ var EventShipper = class {
|
|
|
455
705
|
endpoint: url
|
|
456
706
|
});
|
|
457
707
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
708
|
+
if (this.localDebuggerUrl) {
|
|
709
|
+
mirrorPartialEventToLocalDebugger(payload, {
|
|
710
|
+
baseUrl: this.localDebuggerUrl,
|
|
711
|
+
writeKey: this.writeKey,
|
|
712
|
+
debug: this.debug,
|
|
713
|
+
sdkName: this.sdkName
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
if (!this.writeKey) {
|
|
717
|
+
if (!isPending) this.sticky.delete(eventId);
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
const opts = this.requestOpts();
|
|
721
|
+
if (!opts) {
|
|
722
|
+
this.warnShutdownDrop(`track_partial ${eventId}`);
|
|
723
|
+
if (!isPending) this.sticky.delete(eventId);
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
const p = postJson(url, payload, this.requestHeaders(), opts);
|
|
463
727
|
this.inFlight.add(p);
|
|
464
728
|
try {
|
|
465
729
|
try {
|
|
@@ -469,7 +733,10 @@ var EventShipper = class {
|
|
|
469
733
|
}
|
|
470
734
|
} catch (err) {
|
|
471
735
|
const msg = err instanceof Error ? err.message : String(err);
|
|
472
|
-
|
|
736
|
+
rateLimitedLog(
|
|
737
|
+
`${this.prefix}.send_track_partial_failed`,
|
|
738
|
+
() => console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`)
|
|
739
|
+
);
|
|
473
740
|
}
|
|
474
741
|
} finally {
|
|
475
742
|
this.inFlight.delete(p);
|
|
@@ -479,28 +746,114 @@ var EventShipper = class {
|
|
|
479
746
|
}
|
|
480
747
|
}
|
|
481
748
|
};
|
|
482
|
-
var
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
749
|
+
var DEFAULT_SECRET_KEY_NAMES = [
|
|
750
|
+
"apikey",
|
|
751
|
+
"apisecret",
|
|
752
|
+
"apitoken",
|
|
753
|
+
"secretaccesskey",
|
|
754
|
+
"sessiontoken",
|
|
755
|
+
"privatekey",
|
|
756
|
+
"privatekeyid",
|
|
757
|
+
"clientsecret",
|
|
758
|
+
"accesstoken",
|
|
759
|
+
"refreshtoken",
|
|
760
|
+
"oauthtoken",
|
|
761
|
+
"bearertoken",
|
|
762
|
+
"authorization",
|
|
763
|
+
"password",
|
|
764
|
+
"passphrase"
|
|
765
|
+
];
|
|
766
|
+
var REDACTED_PLACEHOLDER = "[REDACTED]";
|
|
767
|
+
function normalizeKeyName(name) {
|
|
768
|
+
return name.toLowerCase().replace(/[-_.]/g, "");
|
|
769
|
+
}
|
|
770
|
+
function redactSecretsInObject(value, options) {
|
|
771
|
+
var _a, _b;
|
|
772
|
+
const normalizedSecretSet = buildSecretSet((_a = options == null ? void 0 : options.secretKeyNames) != null ? _a : DEFAULT_SECRET_KEY_NAMES);
|
|
773
|
+
const placeholder = (_b = options == null ? void 0 : options.placeholder) != null ? _b : REDACTED_PLACEHOLDER;
|
|
774
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
775
|
+
const walk = (node) => {
|
|
776
|
+
if (node === null || typeof node !== "object") return node;
|
|
777
|
+
if (seen.has(node)) return "[CIRCULAR]";
|
|
778
|
+
seen.add(node);
|
|
779
|
+
if (Array.isArray(node)) {
|
|
780
|
+
return node.map((item) => walk(item));
|
|
781
|
+
}
|
|
782
|
+
const out = {};
|
|
783
|
+
for (const [k, v] of Object.entries(node)) {
|
|
784
|
+
if (normalizedSecretSet.has(normalizeKeyName(k))) {
|
|
785
|
+
out[k] = placeholder;
|
|
786
|
+
} else {
|
|
787
|
+
out[k] = walk(v);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
return out;
|
|
791
|
+
};
|
|
792
|
+
return walk(value);
|
|
793
|
+
}
|
|
794
|
+
function buildSecretSet(names) {
|
|
795
|
+
const set = /* @__PURE__ */ new Set();
|
|
796
|
+
for (const name of names) set.add(normalizeKeyName(name));
|
|
797
|
+
return set;
|
|
798
|
+
}
|
|
799
|
+
var DEFAULT_REDACT_ATTRIBUTE_KEYS = [
|
|
800
|
+
"ai.request.providerOptions",
|
|
801
|
+
"ai.response.providerMetadata"
|
|
802
|
+
];
|
|
803
|
+
function defaultTransformSpan(span) {
|
|
804
|
+
const attrs = span.attributes;
|
|
805
|
+
if (!attrs || attrs.length === 0) return span;
|
|
806
|
+
let nextAttrs;
|
|
807
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
808
|
+
const attr = attrs[i];
|
|
809
|
+
const redacted = redactJsonAttributeValue(attr.key, attr.value);
|
|
810
|
+
if (redacted === void 0) continue;
|
|
811
|
+
if (!nextAttrs) nextAttrs = attrs.slice();
|
|
812
|
+
nextAttrs[i] = { key: attr.key, value: redacted };
|
|
813
|
+
}
|
|
814
|
+
if (!nextAttrs) return span;
|
|
815
|
+
return { ...span, attributes: nextAttrs };
|
|
816
|
+
}
|
|
817
|
+
var REDACT_JSON_ATTRIBUTE_KEYS = new Set(DEFAULT_REDACT_ATTRIBUTE_KEYS);
|
|
818
|
+
function redactJsonAttributeValue(key, value) {
|
|
819
|
+
if (!REDACT_JSON_ATTRIBUTE_KEYS.has(key)) return void 0;
|
|
820
|
+
const json = value.stringValue;
|
|
821
|
+
if (typeof json !== "string" || json.length === 0) return void 0;
|
|
822
|
+
let parsed;
|
|
823
|
+
try {
|
|
824
|
+
parsed = JSON.parse(json);
|
|
825
|
+
} catch (e) {
|
|
826
|
+
return void 0;
|
|
827
|
+
}
|
|
828
|
+
const scrubbed = redactSecretsInObject(parsed);
|
|
829
|
+
let scrubbedJson;
|
|
830
|
+
try {
|
|
831
|
+
scrubbedJson = JSON.stringify(scrubbed);
|
|
832
|
+
} catch (e) {
|
|
833
|
+
return void 0;
|
|
834
|
+
}
|
|
835
|
+
if (scrubbedJson === json) return void 0;
|
|
836
|
+
return { stringValue: scrubbedJson };
|
|
487
837
|
}
|
|
488
|
-
function
|
|
489
|
-
var _a;
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
})
|
|
838
|
+
function applyOtelSpanAttributeLimit(limit) {
|
|
839
|
+
var _a, _b;
|
|
840
|
+
try {
|
|
841
|
+
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;
|
|
842
|
+
if (!raw) return limit;
|
|
843
|
+
const parsed = Number.parseInt(raw, 10);
|
|
844
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
845
|
+
return Math.min(limit, parsed);
|
|
846
|
+
}
|
|
847
|
+
} catch (e) {
|
|
848
|
+
}
|
|
849
|
+
return limit;
|
|
498
850
|
}
|
|
499
851
|
var TraceShipper = class {
|
|
500
852
|
constructor(opts) {
|
|
501
853
|
this.queue = [];
|
|
502
854
|
this.inFlight = /* @__PURE__ */ new Set();
|
|
503
|
-
|
|
855
|
+
this.hasShutdown = false;
|
|
856
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
504
857
|
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
505
858
|
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
506
859
|
this.enabled = opts.enabled !== false;
|
|
@@ -513,13 +866,83 @@ var TraceShipper = class {
|
|
|
513
866
|
this.prefix = `[raindrop-ai/${this.sdkName}]`;
|
|
514
867
|
this.serviceName = (_g = opts.serviceName) != null ? _g : "raindrop.core";
|
|
515
868
|
this.serviceVersion = (_h = opts.serviceVersion) != null ? _h : "0.0.0";
|
|
516
|
-
|
|
517
|
-
if (
|
|
518
|
-
this.
|
|
519
|
-
|
|
520
|
-
|
|
869
|
+
this.localDebuggerUrl = (_i = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _i : void 0;
|
|
870
|
+
if (this.debug && this.localDebuggerUrl) {
|
|
871
|
+
console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
|
|
872
|
+
}
|
|
873
|
+
this.projectId = normalizeProjectId(opts.projectId, {
|
|
874
|
+
debug: this.debug,
|
|
875
|
+
prefix: this.prefix
|
|
876
|
+
});
|
|
877
|
+
this.transformSpanHook = opts.transformSpan;
|
|
878
|
+
this.disableDefaultRedaction = opts.disableDefaultRedaction === true;
|
|
879
|
+
this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* Cap every string attribute value on the span. O(#attributes) length
|
|
883
|
+
* checks; only oversized values pay a slice. Runs AFTER the redaction
|
|
884
|
+
* pipeline so the default secret-scrub still sees parseable JSON in
|
|
885
|
+
* `ai.request.providerOptions` / `ai.response.providerMetadata` (capping
|
|
886
|
+
* first could cut a JSON blob mid-way, fail the parse, and ship secrets
|
|
887
|
+
* in the surviving prefix).
|
|
888
|
+
*
|
|
889
|
+
* A stricter `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var is honored
|
|
890
|
+
* for span content, matching the Python SDK and the OTel SDK convention.
|
|
891
|
+
*/
|
|
892
|
+
capSpanAttributes(span) {
|
|
893
|
+
var _a;
|
|
894
|
+
const maxChars = applyOtelSpanAttributeLimit(
|
|
895
|
+
resolveMaxTextFieldChars(this.maxTextFieldCharsOpt)
|
|
896
|
+
);
|
|
897
|
+
const attrs = span.attributes;
|
|
898
|
+
if (!attrs || attrs.length === 0) return span;
|
|
899
|
+
let nextAttrs;
|
|
900
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
901
|
+
const attr = attrs[i];
|
|
902
|
+
const value = (_a = attr.value) == null ? void 0 : _a.stringValue;
|
|
903
|
+
if (typeof value !== "string" || value.length <= maxChars) continue;
|
|
904
|
+
if (!nextAttrs) nextAttrs = attrs.slice();
|
|
905
|
+
nextAttrs[i] = {
|
|
906
|
+
key: attr.key,
|
|
907
|
+
value: { ...attr.value, stringValue: capText(value, maxChars) }
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
if (!nextAttrs) return span;
|
|
911
|
+
return { ...span, attributes: nextAttrs };
|
|
912
|
+
}
|
|
913
|
+
/**
|
|
914
|
+
* Apply the user `transformSpan` hook (if any) followed by the default
|
|
915
|
+
* redactor (unless disabled). Returns either the (possibly new) span to
|
|
916
|
+
* ship, or `null` to drop the span entirely.
|
|
917
|
+
*
|
|
918
|
+
* Ordering: user hook runs first so callers can rewrite the span freely
|
|
919
|
+
* (rename attrs, add new ones, scrub things the default doesn't know
|
|
920
|
+
* about). The default redactor then runs on whatever the user produced,
|
|
921
|
+
* acting as the always-on floor for documented BYOK secrets. If the user
|
|
922
|
+
* sets `disableDefaultRedaction: true`, the floor is skipped.
|
|
923
|
+
*
|
|
924
|
+
* Fail-closed: if the user hook throws, the span is dropped — a buggy
|
|
925
|
+
* hook can never accidentally ship raw, un-redacted spans.
|
|
926
|
+
*/
|
|
927
|
+
redactSpan(span) {
|
|
928
|
+
let current = span;
|
|
929
|
+
if (this.transformSpanHook) {
|
|
930
|
+
try {
|
|
931
|
+
const result = this.transformSpanHook(current);
|
|
932
|
+
if (result === null) return null;
|
|
933
|
+
if (result !== void 0) current = result;
|
|
934
|
+
} catch (err) {
|
|
935
|
+
if (this.debug) {
|
|
936
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
937
|
+
console.warn(`${this.prefix} transformSpan hook threw: ${msg}`);
|
|
938
|
+
}
|
|
939
|
+
return null;
|
|
521
940
|
}
|
|
522
941
|
}
|
|
942
|
+
if (!this.disableDefaultRedaction) {
|
|
943
|
+
current = defaultTransformSpan(current);
|
|
944
|
+
}
|
|
945
|
+
return this.capSpanAttributes(current);
|
|
523
946
|
}
|
|
524
947
|
isDebugEnabled() {
|
|
525
948
|
return this.debug;
|
|
@@ -527,6 +950,9 @@ var TraceShipper = class {
|
|
|
527
950
|
authHeaders() {
|
|
528
951
|
return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
|
|
529
952
|
}
|
|
953
|
+
requestHeaders() {
|
|
954
|
+
return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
|
|
955
|
+
}
|
|
530
956
|
startSpan(args) {
|
|
531
957
|
var _a, _b;
|
|
532
958
|
const ids = createSpanIds(args.parent);
|
|
@@ -537,8 +963,8 @@ var TraceShipper = class {
|
|
|
537
963
|
];
|
|
538
964
|
if ((_b = args.attributes) == null ? void 0 : _b.length) attrs.push(...args.attributes);
|
|
539
965
|
const span = { ids, name: args.name, startTimeUnixNano: started, attributes: attrs };
|
|
540
|
-
|
|
541
|
-
|
|
966
|
+
this.mirrorToLocalDebugger(
|
|
967
|
+
buildOtlpSpan({
|
|
542
968
|
ids: span.ids,
|
|
543
969
|
name: span.name,
|
|
544
970
|
startTimeUnixNano: span.startTimeUnixNano,
|
|
@@ -546,16 +972,21 @@ var TraceShipper = class {
|
|
|
546
972
|
// placeholder — will be updated on endSpan
|
|
547
973
|
attributes: span.attributes,
|
|
548
974
|
status: { code: SpanStatusCode.UNSET }
|
|
549
|
-
})
|
|
550
|
-
|
|
551
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
552
|
-
baseUrl: this.localDebuggerUrl,
|
|
553
|
-
debug: false,
|
|
554
|
-
sdkName: this.sdkName
|
|
555
|
-
});
|
|
556
|
-
}
|
|
975
|
+
})
|
|
976
|
+
);
|
|
557
977
|
return span;
|
|
558
978
|
}
|
|
979
|
+
mirrorToLocalDebugger(span) {
|
|
980
|
+
if (!this.localDebuggerUrl) return;
|
|
981
|
+
const redacted = this.redactSpan(span);
|
|
982
|
+
if (redacted === null) return;
|
|
983
|
+
const body = buildExportTraceServiceRequest([redacted], this.serviceName, this.serviceVersion);
|
|
984
|
+
mirrorTraceExportToLocalDebugger(body, {
|
|
985
|
+
baseUrl: this.localDebuggerUrl,
|
|
986
|
+
debug: false,
|
|
987
|
+
sdkName: this.sdkName
|
|
988
|
+
});
|
|
989
|
+
}
|
|
559
990
|
endSpan(span, extra) {
|
|
560
991
|
var _a, _b;
|
|
561
992
|
if (span.endTimeUnixNano) return;
|
|
@@ -577,14 +1008,7 @@ var TraceShipper = class {
|
|
|
577
1008
|
status
|
|
578
1009
|
});
|
|
579
1010
|
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
|
-
}
|
|
1011
|
+
this.mirrorToLocalDebugger(otlp);
|
|
588
1012
|
}
|
|
589
1013
|
createSpan(args) {
|
|
590
1014
|
var _a;
|
|
@@ -602,14 +1026,7 @@ var TraceShipper = class {
|
|
|
602
1026
|
status: args.status
|
|
603
1027
|
});
|
|
604
1028
|
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
|
-
}
|
|
1029
|
+
this.mirrorToLocalDebugger(otlp);
|
|
613
1030
|
}
|
|
614
1031
|
enqueue(span) {
|
|
615
1032
|
if (!this.enabled) return;
|
|
@@ -621,10 +1038,12 @@ var TraceShipper = class {
|
|
|
621
1038
|
)}`
|
|
622
1039
|
);
|
|
623
1040
|
}
|
|
1041
|
+
const redacted = this.redactSpan(span);
|
|
1042
|
+
if (redacted === null) return;
|
|
624
1043
|
if (this.queue.length >= this.maxQueueSize) {
|
|
625
1044
|
this.queue.shift();
|
|
626
1045
|
}
|
|
627
|
-
this.queue.push(
|
|
1046
|
+
this.queue.push(redacted);
|
|
628
1047
|
if (this.queue.length >= this.maxBatchSize) {
|
|
629
1048
|
void this.flush().catch(() => {
|
|
630
1049
|
});
|
|
@@ -646,6 +1065,17 @@ var TraceShipper = class {
|
|
|
646
1065
|
}
|
|
647
1066
|
while (this.queue.length > 0) {
|
|
648
1067
|
const batch = this.queue.splice(0, this.maxBatchSize);
|
|
1068
|
+
if (!this.writeKey) continue;
|
|
1069
|
+
const opts = this.requestOpts();
|
|
1070
|
+
if (!opts) {
|
|
1071
|
+
rateLimitedLog(
|
|
1072
|
+
`${this.prefix}.shutdown_deadline`,
|
|
1073
|
+
() => console.warn(
|
|
1074
|
+
`${this.prefix} shutdown flush deadline exceeded; dropping ${batch.length} spans`
|
|
1075
|
+
)
|
|
1076
|
+
);
|
|
1077
|
+
continue;
|
|
1078
|
+
}
|
|
649
1079
|
const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
|
|
650
1080
|
const url = `${this.baseUrl}traces`;
|
|
651
1081
|
if (this.debug) {
|
|
@@ -654,11 +1084,7 @@ var TraceShipper = class {
|
|
|
654
1084
|
endpoint: url
|
|
655
1085
|
});
|
|
656
1086
|
}
|
|
657
|
-
const p = postJson(url, body, this.
|
|
658
|
-
maxAttempts: 3,
|
|
659
|
-
debug: this.debug,
|
|
660
|
-
sdkName: this.sdkName
|
|
661
|
-
});
|
|
1087
|
+
const p = postJson(url, body, this.requestHeaders(), opts);
|
|
662
1088
|
this.inFlight.add(p);
|
|
663
1089
|
try {
|
|
664
1090
|
try {
|
|
@@ -666,21 +1092,61 @@ var TraceShipper = class {
|
|
|
666
1092
|
if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
|
|
667
1093
|
} catch (err) {
|
|
668
1094
|
const msg = err instanceof Error ? err.message : String(err);
|
|
669
|
-
|
|
1095
|
+
rateLimitedLog(
|
|
1096
|
+
`${this.prefix}.send_spans_failed`,
|
|
1097
|
+
() => console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`)
|
|
1098
|
+
);
|
|
670
1099
|
}
|
|
671
1100
|
} finally {
|
|
672
1101
|
this.inFlight.delete(p);
|
|
673
1102
|
}
|
|
674
1103
|
}
|
|
675
1104
|
}
|
|
1105
|
+
/** See EventShipper.requestOpts — same shutdown-budget semantics. */
|
|
1106
|
+
requestOpts() {
|
|
1107
|
+
if (this.shutdownDeadlineAt !== void 0) {
|
|
1108
|
+
const remainingMs = this.shutdownDeadlineAt - Date.now();
|
|
1109
|
+
if (remainingMs <= 0) return null;
|
|
1110
|
+
return {
|
|
1111
|
+
maxAttempts: 1,
|
|
1112
|
+
debug: this.debug,
|
|
1113
|
+
sdkName: this.sdkName,
|
|
1114
|
+
timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
if (this.hasShutdown) {
|
|
1118
|
+
return {
|
|
1119
|
+
maxAttempts: 1,
|
|
1120
|
+
debug: this.debug,
|
|
1121
|
+
sdkName: this.sdkName,
|
|
1122
|
+
timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
|
|
1126
|
+
}
|
|
676
1127
|
async shutdown() {
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
1128
|
+
this.hasShutdown = true;
|
|
1129
|
+
this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
|
|
1130
|
+
try {
|
|
1131
|
+
if (this.timer) {
|
|
1132
|
+
clearTimeout(this.timer);
|
|
1133
|
+
this.timer = void 0;
|
|
1134
|
+
}
|
|
1135
|
+
const drain = async () => {
|
|
1136
|
+
await this.flush();
|
|
1137
|
+
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
1138
|
+
})));
|
|
1139
|
+
};
|
|
1140
|
+
const settled = await raceWithTimeout(drain(), SHUTDOWN_DEADLINE_MS);
|
|
1141
|
+
if (!settled) {
|
|
1142
|
+
rateLimitedLog(
|
|
1143
|
+
`${this.prefix}.shutdown_deadline`,
|
|
1144
|
+
() => console.warn(`${this.prefix} shutdown flush deadline exceeded; abandoning in-flight spans`)
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
} finally {
|
|
1148
|
+
this.shutdownDeadlineAt = void 0;
|
|
680
1149
|
}
|
|
681
|
-
await this.flush();
|
|
682
|
-
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
683
|
-
})));
|
|
684
1150
|
}
|
|
685
1151
|
};
|
|
686
1152
|
|
|
@@ -733,7 +1199,7 @@ var import_node_os = require("os");
|
|
|
733
1199
|
var import_node_path = require("path");
|
|
734
1200
|
var CONFIG_PATH = (0, import_node_path.join)((0, import_node_os.homedir)(), ".config", "raindrop", "config.json");
|
|
735
1201
|
function loadConfig() {
|
|
736
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
1202
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
|
737
1203
|
let file = {};
|
|
738
1204
|
try {
|
|
739
1205
|
if ((0, import_node_fs.existsSync)(CONFIG_PATH)) {
|
|
@@ -773,9 +1239,10 @@ function loadConfig() {
|
|
|
773
1239
|
return {
|
|
774
1240
|
writeKey: (_c = (_b = process.env["RAINDROP_WRITE_KEY"]) != null ? _b : file.write_key) != null ? _c : "",
|
|
775
1241
|
endpoint: (_e = (_d = process.env["RAINDROP_API_URL"]) != null ? _d : file.api_url) != null ? _e : "https://api.raindrop.ai/v1",
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
1242
|
+
projectId: (_f = process.env["RAINDROP_PROJECT_ID"]) != null ? _f : file.project_id,
|
|
1243
|
+
userId: (_h = (_g = process.env["RAINDROP_USER_ID"]) != null ? _g : file.user_id) != null ? _h : systemUser,
|
|
1244
|
+
convoId: ((_i = process.env["RAINDROP_CONVO_ID"]) == null ? void 0 : _i.trim()) || void 0,
|
|
1245
|
+
debug: process.env["RAINDROP_DEBUG"] === "true" ? true : (_j = file.debug) != null ? _j : false,
|
|
779
1246
|
enabled: (() => {
|
|
780
1247
|
var _a2;
|
|
781
1248
|
const env = process.env["RAINDROP_ENABLED"];
|
|
@@ -783,7 +1250,7 @@ function loadConfig() {
|
|
|
783
1250
|
const lower = env.toLowerCase();
|
|
784
1251
|
return lower !== "false" && env !== "0";
|
|
785
1252
|
})(),
|
|
786
|
-
eventName: (
|
|
1253
|
+
eventName: (_l = (_k = process.env["RAINDROP_EVENT_NAME"]) != null ? _k : file.event_name) != null ? _l : "claude_code_session",
|
|
787
1254
|
customProperties,
|
|
788
1255
|
selfDiagnostics
|
|
789
1256
|
};
|
|
@@ -1029,18 +1496,85 @@ function transcriptToProperties(summary) {
|
|
|
1029
1496
|
|
|
1030
1497
|
// src/event-mapper.ts
|
|
1031
1498
|
var MAX_ATTR_LENGTH = 32768;
|
|
1499
|
+
var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
|
|
1500
|
+
var MAX_TEXT_FIELD_CHARS = 1e6;
|
|
1501
|
+
var MAX_BOUNDED_DEPTH = 12;
|
|
1032
1502
|
function truncate(value) {
|
|
1033
1503
|
if (value === void 0) return void 0;
|
|
1034
1504
|
if (value.length <= MAX_ATTR_LENGTH) return value;
|
|
1035
1505
|
const suffix = "\n...[truncated]";
|
|
1036
1506
|
return value.slice(0, MAX_ATTR_LENGTH - suffix.length) + suffix;
|
|
1037
1507
|
}
|
|
1508
|
+
function capText2(value, limit = MAX_TEXT_FIELD_CHARS) {
|
|
1509
|
+
if (value.length <= limit) return value;
|
|
1510
|
+
if (limit > TRUNCATION_MARKER2.length) {
|
|
1511
|
+
return value.slice(0, limit - TRUNCATION_MARKER2.length) + TRUNCATION_MARKER2;
|
|
1512
|
+
}
|
|
1513
|
+
return value.slice(0, limit);
|
|
1514
|
+
}
|
|
1515
|
+
function boundedClone2(value, budget, depth) {
|
|
1516
|
+
if (budget.remaining <= 0) return TRUNCATION_MARKER2;
|
|
1517
|
+
if (typeof value === "string") {
|
|
1518
|
+
if (value.length > budget.remaining) {
|
|
1519
|
+
const taken = value.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
|
|
1520
|
+
budget.remaining = 0;
|
|
1521
|
+
return taken;
|
|
1522
|
+
}
|
|
1523
|
+
budget.remaining -= Math.max(value.length, 1);
|
|
1524
|
+
return value;
|
|
1525
|
+
}
|
|
1526
|
+
if (value === null || typeof value === "number" || typeof value === "boolean") {
|
|
1527
|
+
budget.remaining -= 8;
|
|
1528
|
+
return value;
|
|
1529
|
+
}
|
|
1530
|
+
if (typeof value !== "object") {
|
|
1531
|
+
budget.remaining -= 8;
|
|
1532
|
+
return value;
|
|
1533
|
+
}
|
|
1534
|
+
if (depth >= MAX_BOUNDED_DEPTH) {
|
|
1535
|
+
budget.remaining -= 16;
|
|
1536
|
+
return `<max depth: ${TRUNCATION_MARKER2}>`;
|
|
1537
|
+
}
|
|
1538
|
+
const withToJson = value;
|
|
1539
|
+
if (typeof withToJson.toJSON === "function") {
|
|
1540
|
+
try {
|
|
1541
|
+
return boundedClone2(withToJson.toJSON(), budget, depth + 1);
|
|
1542
|
+
} catch (e) {
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
if (Array.isArray(value)) {
|
|
1546
|
+
const out2 = [];
|
|
1547
|
+
for (const item of value) {
|
|
1548
|
+
if (budget.remaining <= 0) {
|
|
1549
|
+
out2.push(TRUNCATION_MARKER2);
|
|
1550
|
+
break;
|
|
1551
|
+
}
|
|
1552
|
+
out2.push(boundedClone2(item, budget, depth + 1));
|
|
1553
|
+
}
|
|
1554
|
+
return out2;
|
|
1555
|
+
}
|
|
1556
|
+
const out = {};
|
|
1557
|
+
for (const key of Object.keys(value)) {
|
|
1558
|
+
if (budget.remaining <= 0) {
|
|
1559
|
+
out["..."] = TRUNCATION_MARKER2;
|
|
1560
|
+
break;
|
|
1561
|
+
}
|
|
1562
|
+
budget.remaining -= Math.max(key.length, 1);
|
|
1563
|
+
out[key] = boundedClone2(value[key], budget, depth + 1);
|
|
1564
|
+
}
|
|
1565
|
+
return out;
|
|
1566
|
+
}
|
|
1038
1567
|
function safeStringify(value) {
|
|
1039
1568
|
if (value === void 0 || value === null) return void 0;
|
|
1040
1569
|
try {
|
|
1041
|
-
|
|
1570
|
+
const pruned = boundedClone2(
|
|
1571
|
+
value,
|
|
1572
|
+
{ remaining: MAX_ATTR_LENGTH + TRUNCATION_MARKER2.length + 256 },
|
|
1573
|
+
0
|
|
1574
|
+
);
|
|
1575
|
+
return truncate(JSON.stringify(pruned));
|
|
1042
1576
|
} catch (e) {
|
|
1043
|
-
return truncate(String(value));
|
|
1577
|
+
return truncate(capText2(String(value), MAX_ATTR_LENGTH + 16));
|
|
1044
1578
|
}
|
|
1045
1579
|
}
|
|
1046
1580
|
var STATE_DIR = (0, import_node_path2.join)((0, import_node_os2.tmpdir)(), "raindrop-claude-code");
|
|
@@ -1221,7 +1755,9 @@ async function handleUserPromptSubmit(payload, convoId, config, properties, even
|
|
|
1221
1755
|
userId: config.userId,
|
|
1222
1756
|
convoId,
|
|
1223
1757
|
eventName: config.eventName,
|
|
1224
|
-
|
|
1758
|
+
// Capped so a pasted multi-MB prompt can't produce an event that the
|
|
1759
|
+
// ingest API rejects (silent data loss) — it lands truncated instead.
|
|
1760
|
+
input: payload.prompt !== void 0 ? capText2(payload.prompt) : void 0,
|
|
1225
1761
|
model,
|
|
1226
1762
|
properties
|
|
1227
1763
|
};
|
|
@@ -1344,7 +1880,7 @@ async function handlePostCompact(payload, eventId, convoId, config, properties,
|
|
|
1344
1880
|
properties: {
|
|
1345
1881
|
...properties,
|
|
1346
1882
|
compaction_trigger: payload.trigger,
|
|
1347
|
-
compact_summary: payload.compact_summary
|
|
1883
|
+
compact_summary: payload.compact_summary !== void 0 ? capText2(payload.compact_summary) : void 0
|
|
1348
1884
|
}
|
|
1349
1885
|
});
|
|
1350
1886
|
}
|
|
@@ -1579,7 +2115,7 @@ function enrichFromTranscript(payload, eventId, traceShipper) {
|
|
|
1579
2115
|
attrInt("gen_ai.usage.prompt_tokens", phase.inputTokens),
|
|
1580
2116
|
attrInt("gen_ai.usage.completion_tokens", phase.outputTokens),
|
|
1581
2117
|
...phase.cacheReadTokens > 0 ? [attrInt("gen_ai.usage.cache_read_tokens", phase.cacheReadTokens)] : [],
|
|
1582
|
-
attrString("ai.response.text", outputText),
|
|
2118
|
+
attrString("ai.response.text", truncate(outputText)),
|
|
1583
2119
|
...toolCallNames ? [attrString("ai.toolCall.names", toolCallNames)] : [],
|
|
1584
2120
|
...phase.hasThinking ? [attrString("ai.has_thinking", "true")] : [],
|
|
1585
2121
|
attrInt("ai.llm_call.index", emittedPhaseIndex)
|
|
@@ -1639,7 +2175,7 @@ async function handleStopOrFailure(payload, eventId, convoId, config, properties
|
|
|
1639
2175
|
userId: config.userId,
|
|
1640
2176
|
convoId,
|
|
1641
2177
|
eventName: config.eventName,
|
|
1642
|
-
output,
|
|
2178
|
+
output: output !== void 0 ? capText2(output) : void 0,
|
|
1643
2179
|
...(summary == null ? void 0 : summary.model) ? { model: summary.model } : {},
|
|
1644
2180
|
properties: {
|
|
1645
2181
|
...properties,
|
|
@@ -1670,6 +2206,7 @@ var import_node_path3 = require("path");
|
|
|
1670
2206
|
var DEFAULT_PORT = 5899;
|
|
1671
2207
|
var DEFAULT_URL = `http://localhost:${DEFAULT_PORT}/v1/`;
|
|
1672
2208
|
var HEALTH_TIMEOUT_MS = 300;
|
|
2209
|
+
var MIRROR_TIMEOUT_MS = 2e3;
|
|
1673
2210
|
var CACHE_DIR = (0, import_node_path3.join)((0, import_node_os3.tmpdir)(), "raindrop-claude-code");
|
|
1674
2211
|
var CACHE_FILE = (0, import_node_path3.join)(CACHE_DIR, "debugger_cache");
|
|
1675
2212
|
var CACHE_TTL_MS = 5e3;
|
|
@@ -1693,10 +2230,8 @@ function writeCache(url) {
|
|
|
1693
2230
|
async function healthPing(baseUrl) {
|
|
1694
2231
|
const healthUrl = baseUrl.replace(/\/v1\/$/, "/health");
|
|
1695
2232
|
try {
|
|
1696
|
-
const
|
|
1697
|
-
const
|
|
1698
|
-
const resp = await fetch(healthUrl, { signal: controller.signal });
|
|
1699
|
-
clearTimeout(timeout);
|
|
2233
|
+
const signal = AbortSignal.timeout(HEALTH_TIMEOUT_MS);
|
|
2234
|
+
const resp = await fetch(healthUrl, { signal });
|
|
1700
2235
|
if (resp.ok) {
|
|
1701
2236
|
const body = await resp.json();
|
|
1702
2237
|
if (body.ok) return baseUrl;
|
|
@@ -1739,7 +2274,8 @@ function mirrorEventToLocalDebugger(baseUrl, payload, debug) {
|
|
|
1739
2274
|
void fetch(url, {
|
|
1740
2275
|
method: "POST",
|
|
1741
2276
|
headers: { "Content-Type": "application/json" },
|
|
1742
|
-
body: JSON.stringify(payload)
|
|
2277
|
+
body: JSON.stringify(payload),
|
|
2278
|
+
signal: AbortSignal.timeout(MIRROR_TIMEOUT_MS)
|
|
1743
2279
|
}).catch(() => {
|
|
1744
2280
|
});
|
|
1745
2281
|
}
|
|
@@ -1880,6 +2416,7 @@ async function executeTool(args) {
|
|
|
1880
2416
|
writeKey: config.writeKey,
|
|
1881
2417
|
endpoint: config.endpoint,
|
|
1882
2418
|
debug: false,
|
|
2419
|
+
projectId: config.projectId,
|
|
1883
2420
|
enabled: true
|
|
1884
2421
|
});
|
|
1885
2422
|
try {
|