@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.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// ../core/dist/chunk-
|
|
1
|
+
// ../core/dist/chunk-SK6EJEO7.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,112 @@ 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 PROJECT_ID_HEADER = "X-Raindrop-Project-Id";
|
|
343
|
+
var PROJECT_ID_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
344
|
+
function isValidProjectIdSlug(value) {
|
|
345
|
+
return PROJECT_ID_SLUG_PATTERN.test(value);
|
|
346
|
+
}
|
|
347
|
+
function normalizeProjectId(raw, opts) {
|
|
348
|
+
if (typeof raw !== "string") return void 0;
|
|
349
|
+
const trimmed = raw.trim();
|
|
350
|
+
if (!trimmed) return void 0;
|
|
351
|
+
if (!isValidProjectIdSlug(trimmed) && opts.debug) {
|
|
352
|
+
console.warn(
|
|
353
|
+
`${opts.prefix} projectId "${trimmed}" does not match slug ${PROJECT_ID_SLUG_PATTERN.source}; sending anyway \u2014 backend may reject with HTTP 400`
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
return trimmed;
|
|
357
|
+
}
|
|
358
|
+
function projectIdHeaders(projectId) {
|
|
359
|
+
return projectId ? { [PROJECT_ID_HEADER]: projectId } : {};
|
|
360
|
+
}
|
|
361
|
+
var SHUTDOWN_DEADLINE_MS = 1e4;
|
|
362
|
+
var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
191
363
|
function mergePatches(target, source) {
|
|
192
364
|
var _a, _b, _c, _d;
|
|
193
365
|
const out = { ...target, ...source };
|
|
@@ -205,7 +377,8 @@ var EventShipper = class {
|
|
|
205
377
|
this.sticky = /* @__PURE__ */ new Map();
|
|
206
378
|
this.timers = /* @__PURE__ */ new Map();
|
|
207
379
|
this.inFlight = /* @__PURE__ */ new Set();
|
|
208
|
-
|
|
380
|
+
this.hasShutdown = false;
|
|
381
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
209
382
|
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
210
383
|
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
211
384
|
this.enabled = opts.enabled !== false;
|
|
@@ -214,11 +387,20 @@ var EventShipper = class {
|
|
|
214
387
|
this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
|
|
215
388
|
this.prefix = `[raindrop-ai/${this.sdkName}]`;
|
|
216
389
|
this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
|
|
390
|
+
this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
|
|
391
|
+
this.localDebuggerUrl = (_f = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _f : void 0;
|
|
392
|
+
if (this.debug && this.localDebuggerUrl) {
|
|
393
|
+
console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
|
|
394
|
+
}
|
|
395
|
+
this.projectId = normalizeProjectId(opts.projectId, {
|
|
396
|
+
debug: this.debug,
|
|
397
|
+
prefix: this.prefix
|
|
398
|
+
});
|
|
217
399
|
const isNode = typeof process !== "undefined" && typeof process.version === "string";
|
|
218
400
|
this.context = {
|
|
219
401
|
library: {
|
|
220
|
-
name: (
|
|
221
|
-
version: (
|
|
402
|
+
name: (_g = opts.libraryName) != null ? _g : "@raindrop-ai/core",
|
|
403
|
+
version: (_h = opts.libraryVersion) != null ? _h : "0.0.0"
|
|
222
404
|
},
|
|
223
405
|
metadata: {
|
|
224
406
|
jsRuntime: isNode ? "node" : "web",
|
|
@@ -232,10 +414,55 @@ var EventShipper = class {
|
|
|
232
414
|
authHeaders() {
|
|
233
415
|
return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
|
|
234
416
|
}
|
|
417
|
+
requestHeaders() {
|
|
418
|
+
return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Build the retry/timeout options for one POST, honoring the shutdown
|
|
422
|
+
* deadline. Returns `null` when the shutdown drain window is exhausted —
|
|
423
|
+
* the caller must drop the payload (with a rate-limited warning) instead
|
|
424
|
+
* of issuing a request that could outlive process exit.
|
|
425
|
+
*
|
|
426
|
+
* Checked fresh on EVERY send, so a shutdown that begins while the flush
|
|
427
|
+
* path is mid-drain takes effect immediately: no further retries, and the
|
|
428
|
+
* per-attempt timeout is clamped to the remaining window. After
|
|
429
|
+
* `shutdown()` returns (deadline cleared, `hasShutdown` still set),
|
|
430
|
+
* sends — late callers, or flush work the deadline abandoned mid-drain —
|
|
431
|
+
* run as a single short attempt rather than regaining the full retry
|
|
432
|
+
* schedule.
|
|
433
|
+
*/
|
|
434
|
+
requestOpts() {
|
|
435
|
+
if (this.shutdownDeadlineAt !== void 0) {
|
|
436
|
+
const remainingMs = this.shutdownDeadlineAt - Date.now();
|
|
437
|
+
if (remainingMs <= 0) return null;
|
|
438
|
+
return {
|
|
439
|
+
maxAttempts: 1,
|
|
440
|
+
debug: this.debug,
|
|
441
|
+
sdkName: this.sdkName,
|
|
442
|
+
timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
if (this.hasShutdown) {
|
|
446
|
+
return {
|
|
447
|
+
maxAttempts: 1,
|
|
448
|
+
debug: this.debug,
|
|
449
|
+
sdkName: this.sdkName,
|
|
450
|
+
timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
|
|
454
|
+
}
|
|
235
455
|
async patch(eventId, patch) {
|
|
236
456
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
237
457
|
if (!this.enabled) return;
|
|
238
458
|
if (!eventId || !eventId.trim()) return;
|
|
459
|
+
const maxChars = resolveMaxTextFieldChars(this.maxTextFieldCharsOpt);
|
|
460
|
+
if (typeof patch.input === "string" && patch.input.length > maxChars) {
|
|
461
|
+
patch = { ...patch, input: capText(patch.input, maxChars) };
|
|
462
|
+
}
|
|
463
|
+
if (typeof patch.output === "string" && patch.output.length > maxChars) {
|
|
464
|
+
patch = { ...patch, output: capText(patch.output, maxChars) };
|
|
465
|
+
}
|
|
239
466
|
if (this.debug) {
|
|
240
467
|
console.log(`${this.prefix} queue patch`, {
|
|
241
468
|
eventId,
|
|
@@ -282,9 +509,16 @@ var EventShipper = class {
|
|
|
282
509
|
})));
|
|
283
510
|
}
|
|
284
511
|
async shutdown() {
|
|
285
|
-
|
|
286
|
-
this.
|
|
287
|
-
|
|
512
|
+
this.hasShutdown = true;
|
|
513
|
+
this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
|
|
514
|
+
try {
|
|
515
|
+
for (const t of this.timers.values()) clearTimeout(t);
|
|
516
|
+
this.timers.clear();
|
|
517
|
+
const settled = await raceWithTimeout(this.flush(), SHUTDOWN_DEADLINE_MS);
|
|
518
|
+
if (!settled) this.warnShutdownDrop("in-flight request(s) at shutdown");
|
|
519
|
+
} finally {
|
|
520
|
+
this.shutdownDeadlineAt = void 0;
|
|
521
|
+
}
|
|
288
522
|
}
|
|
289
523
|
async trackSignal(signal) {
|
|
290
524
|
var _a, _b;
|
|
@@ -304,16 +538,21 @@ var EventShipper = class {
|
|
|
304
538
|
}
|
|
305
539
|
}
|
|
306
540
|
];
|
|
541
|
+
if (!this.writeKey) return;
|
|
307
542
|
const url = `${this.baseUrl}signals/track`;
|
|
543
|
+
const opts = this.requestOpts();
|
|
544
|
+
if (!opts) {
|
|
545
|
+
this.warnShutdownDrop("signal");
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
308
548
|
try {
|
|
309
|
-
await postJson(url, body, this.
|
|
310
|
-
maxAttempts: 3,
|
|
311
|
-
debug: this.debug,
|
|
312
|
-
sdkName: this.sdkName
|
|
313
|
-
});
|
|
549
|
+
await postJson(url, body, this.requestHeaders(), opts);
|
|
314
550
|
} catch (err) {
|
|
315
551
|
const msg = err instanceof Error ? err.message : String(err);
|
|
316
|
-
|
|
552
|
+
rateLimitedLog(
|
|
553
|
+
`${this.prefix}.send_signal_failed`,
|
|
554
|
+
() => console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`)
|
|
555
|
+
);
|
|
317
556
|
}
|
|
318
557
|
}
|
|
319
558
|
async identify(users) {
|
|
@@ -334,19 +573,30 @@ var EventShipper = class {
|
|
|
334
573
|
traits: (_a = user.traits) != null ? _a : {}
|
|
335
574
|
};
|
|
336
575
|
});
|
|
576
|
+
if (!this.writeKey) return;
|
|
337
577
|
if (body.length === 0) return;
|
|
338
578
|
const url = `${this.baseUrl}users/identify`;
|
|
579
|
+
const opts = this.requestOpts();
|
|
580
|
+
if (!opts) {
|
|
581
|
+
this.warnShutdownDrop("identify");
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
339
584
|
try {
|
|
340
|
-
await postJson(url, body, this.
|
|
341
|
-
maxAttempts: 3,
|
|
342
|
-
debug: this.debug,
|
|
343
|
-
sdkName: this.sdkName
|
|
344
|
-
});
|
|
585
|
+
await postJson(url, body, this.requestHeaders(), opts);
|
|
345
586
|
} catch (err) {
|
|
346
587
|
const msg = err instanceof Error ? err.message : String(err);
|
|
347
|
-
|
|
588
|
+
rateLimitedLog(
|
|
589
|
+
`${this.prefix}.send_identify_failed`,
|
|
590
|
+
() => console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`)
|
|
591
|
+
);
|
|
348
592
|
}
|
|
349
593
|
}
|
|
594
|
+
warnShutdownDrop(what) {
|
|
595
|
+
rateLimitedLog(
|
|
596
|
+
`${this.prefix}.shutdown_deadline`,
|
|
597
|
+
() => console.warn(`${this.prefix} shutdown flush deadline exceeded; dropping ${what}`)
|
|
598
|
+
);
|
|
599
|
+
}
|
|
350
600
|
async flushOne(eventId) {
|
|
351
601
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
352
602
|
if (!this.enabled) return;
|
|
@@ -410,11 +660,25 @@ var EventShipper = class {
|
|
|
410
660
|
endpoint: url
|
|
411
661
|
});
|
|
412
662
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
663
|
+
if (this.localDebuggerUrl) {
|
|
664
|
+
mirrorPartialEventToLocalDebugger(payload, {
|
|
665
|
+
baseUrl: this.localDebuggerUrl,
|
|
666
|
+
writeKey: this.writeKey,
|
|
667
|
+
debug: this.debug,
|
|
668
|
+
sdkName: this.sdkName
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
if (!this.writeKey) {
|
|
672
|
+
if (!isPending) this.sticky.delete(eventId);
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
const opts = this.requestOpts();
|
|
676
|
+
if (!opts) {
|
|
677
|
+
this.warnShutdownDrop(`track_partial ${eventId}`);
|
|
678
|
+
if (!isPending) this.sticky.delete(eventId);
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
const p = postJson(url, payload, this.requestHeaders(), opts);
|
|
418
682
|
this.inFlight.add(p);
|
|
419
683
|
try {
|
|
420
684
|
try {
|
|
@@ -424,7 +688,10 @@ var EventShipper = class {
|
|
|
424
688
|
}
|
|
425
689
|
} catch (err) {
|
|
426
690
|
const msg = err instanceof Error ? err.message : String(err);
|
|
427
|
-
|
|
691
|
+
rateLimitedLog(
|
|
692
|
+
`${this.prefix}.send_track_partial_failed`,
|
|
693
|
+
() => console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`)
|
|
694
|
+
);
|
|
428
695
|
}
|
|
429
696
|
} finally {
|
|
430
697
|
this.inFlight.delete(p);
|
|
@@ -434,28 +701,114 @@ var EventShipper = class {
|
|
|
434
701
|
}
|
|
435
702
|
}
|
|
436
703
|
};
|
|
437
|
-
var
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
704
|
+
var DEFAULT_SECRET_KEY_NAMES = [
|
|
705
|
+
"apikey",
|
|
706
|
+
"apisecret",
|
|
707
|
+
"apitoken",
|
|
708
|
+
"secretaccesskey",
|
|
709
|
+
"sessiontoken",
|
|
710
|
+
"privatekey",
|
|
711
|
+
"privatekeyid",
|
|
712
|
+
"clientsecret",
|
|
713
|
+
"accesstoken",
|
|
714
|
+
"refreshtoken",
|
|
715
|
+
"oauthtoken",
|
|
716
|
+
"bearertoken",
|
|
717
|
+
"authorization",
|
|
718
|
+
"password",
|
|
719
|
+
"passphrase"
|
|
720
|
+
];
|
|
721
|
+
var REDACTED_PLACEHOLDER = "[REDACTED]";
|
|
722
|
+
function normalizeKeyName(name) {
|
|
723
|
+
return name.toLowerCase().replace(/[-_.]/g, "");
|
|
724
|
+
}
|
|
725
|
+
function redactSecretsInObject(value, options) {
|
|
726
|
+
var _a, _b;
|
|
727
|
+
const normalizedSecretSet = buildSecretSet((_a = options == null ? void 0 : options.secretKeyNames) != null ? _a : DEFAULT_SECRET_KEY_NAMES);
|
|
728
|
+
const placeholder = (_b = options == null ? void 0 : options.placeholder) != null ? _b : REDACTED_PLACEHOLDER;
|
|
729
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
730
|
+
const walk = (node) => {
|
|
731
|
+
if (node === null || typeof node !== "object") return node;
|
|
732
|
+
if (seen.has(node)) return "[CIRCULAR]";
|
|
733
|
+
seen.add(node);
|
|
734
|
+
if (Array.isArray(node)) {
|
|
735
|
+
return node.map((item) => walk(item));
|
|
736
|
+
}
|
|
737
|
+
const out = {};
|
|
738
|
+
for (const [k, v] of Object.entries(node)) {
|
|
739
|
+
if (normalizedSecretSet.has(normalizeKeyName(k))) {
|
|
740
|
+
out[k] = placeholder;
|
|
741
|
+
} else {
|
|
742
|
+
out[k] = walk(v);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
return out;
|
|
746
|
+
};
|
|
747
|
+
return walk(value);
|
|
748
|
+
}
|
|
749
|
+
function buildSecretSet(names) {
|
|
750
|
+
const set = /* @__PURE__ */ new Set();
|
|
751
|
+
for (const name of names) set.add(normalizeKeyName(name));
|
|
752
|
+
return set;
|
|
753
|
+
}
|
|
754
|
+
var DEFAULT_REDACT_ATTRIBUTE_KEYS = [
|
|
755
|
+
"ai.request.providerOptions",
|
|
756
|
+
"ai.response.providerMetadata"
|
|
757
|
+
];
|
|
758
|
+
function defaultTransformSpan(span) {
|
|
759
|
+
const attrs = span.attributes;
|
|
760
|
+
if (!attrs || attrs.length === 0) return span;
|
|
761
|
+
let nextAttrs;
|
|
762
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
763
|
+
const attr = attrs[i];
|
|
764
|
+
const redacted = redactJsonAttributeValue(attr.key, attr.value);
|
|
765
|
+
if (redacted === void 0) continue;
|
|
766
|
+
if (!nextAttrs) nextAttrs = attrs.slice();
|
|
767
|
+
nextAttrs[i] = { key: attr.key, value: redacted };
|
|
768
|
+
}
|
|
769
|
+
if (!nextAttrs) return span;
|
|
770
|
+
return { ...span, attributes: nextAttrs };
|
|
771
|
+
}
|
|
772
|
+
var REDACT_JSON_ATTRIBUTE_KEYS = new Set(DEFAULT_REDACT_ATTRIBUTE_KEYS);
|
|
773
|
+
function redactJsonAttributeValue(key, value) {
|
|
774
|
+
if (!REDACT_JSON_ATTRIBUTE_KEYS.has(key)) return void 0;
|
|
775
|
+
const json = value.stringValue;
|
|
776
|
+
if (typeof json !== "string" || json.length === 0) return void 0;
|
|
777
|
+
let parsed;
|
|
778
|
+
try {
|
|
779
|
+
parsed = JSON.parse(json);
|
|
780
|
+
} catch (e) {
|
|
781
|
+
return void 0;
|
|
782
|
+
}
|
|
783
|
+
const scrubbed = redactSecretsInObject(parsed);
|
|
784
|
+
let scrubbedJson;
|
|
785
|
+
try {
|
|
786
|
+
scrubbedJson = JSON.stringify(scrubbed);
|
|
787
|
+
} catch (e) {
|
|
788
|
+
return void 0;
|
|
789
|
+
}
|
|
790
|
+
if (scrubbedJson === json) return void 0;
|
|
791
|
+
return { stringValue: scrubbedJson };
|
|
442
792
|
}
|
|
443
|
-
function
|
|
444
|
-
var _a;
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
})
|
|
793
|
+
function applyOtelSpanAttributeLimit(limit) {
|
|
794
|
+
var _a, _b;
|
|
795
|
+
try {
|
|
796
|
+
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;
|
|
797
|
+
if (!raw) return limit;
|
|
798
|
+
const parsed = Number.parseInt(raw, 10);
|
|
799
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
800
|
+
return Math.min(limit, parsed);
|
|
801
|
+
}
|
|
802
|
+
} catch (e) {
|
|
803
|
+
}
|
|
804
|
+
return limit;
|
|
453
805
|
}
|
|
454
806
|
var TraceShipper = class {
|
|
455
807
|
constructor(opts) {
|
|
456
808
|
this.queue = [];
|
|
457
809
|
this.inFlight = /* @__PURE__ */ new Set();
|
|
458
|
-
|
|
810
|
+
this.hasShutdown = false;
|
|
811
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
459
812
|
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
460
813
|
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
461
814
|
this.enabled = opts.enabled !== false;
|
|
@@ -468,13 +821,83 @@ var TraceShipper = class {
|
|
|
468
821
|
this.prefix = `[raindrop-ai/${this.sdkName}]`;
|
|
469
822
|
this.serviceName = (_g = opts.serviceName) != null ? _g : "raindrop.core";
|
|
470
823
|
this.serviceVersion = (_h = opts.serviceVersion) != null ? _h : "0.0.0";
|
|
471
|
-
|
|
472
|
-
if (
|
|
473
|
-
this.
|
|
474
|
-
|
|
475
|
-
|
|
824
|
+
this.localDebuggerUrl = (_i = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _i : void 0;
|
|
825
|
+
if (this.debug && this.localDebuggerUrl) {
|
|
826
|
+
console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
|
|
827
|
+
}
|
|
828
|
+
this.projectId = normalizeProjectId(opts.projectId, {
|
|
829
|
+
debug: this.debug,
|
|
830
|
+
prefix: this.prefix
|
|
831
|
+
});
|
|
832
|
+
this.transformSpanHook = opts.transformSpan;
|
|
833
|
+
this.disableDefaultRedaction = opts.disableDefaultRedaction === true;
|
|
834
|
+
this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Cap every string attribute value on the span. O(#attributes) length
|
|
838
|
+
* checks; only oversized values pay a slice. Runs AFTER the redaction
|
|
839
|
+
* pipeline so the default secret-scrub still sees parseable JSON in
|
|
840
|
+
* `ai.request.providerOptions` / `ai.response.providerMetadata` (capping
|
|
841
|
+
* first could cut a JSON blob mid-way, fail the parse, and ship secrets
|
|
842
|
+
* in the surviving prefix).
|
|
843
|
+
*
|
|
844
|
+
* A stricter `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var is honored
|
|
845
|
+
* for span content, matching the Python SDK and the OTel SDK convention.
|
|
846
|
+
*/
|
|
847
|
+
capSpanAttributes(span) {
|
|
848
|
+
var _a;
|
|
849
|
+
const maxChars = applyOtelSpanAttributeLimit(
|
|
850
|
+
resolveMaxTextFieldChars(this.maxTextFieldCharsOpt)
|
|
851
|
+
);
|
|
852
|
+
const attrs = span.attributes;
|
|
853
|
+
if (!attrs || attrs.length === 0) return span;
|
|
854
|
+
let nextAttrs;
|
|
855
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
856
|
+
const attr = attrs[i];
|
|
857
|
+
const value = (_a = attr.value) == null ? void 0 : _a.stringValue;
|
|
858
|
+
if (typeof value !== "string" || value.length <= maxChars) continue;
|
|
859
|
+
if (!nextAttrs) nextAttrs = attrs.slice();
|
|
860
|
+
nextAttrs[i] = {
|
|
861
|
+
key: attr.key,
|
|
862
|
+
value: { ...attr.value, stringValue: capText(value, maxChars) }
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
if (!nextAttrs) return span;
|
|
866
|
+
return { ...span, attributes: nextAttrs };
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* Apply the user `transformSpan` hook (if any) followed by the default
|
|
870
|
+
* redactor (unless disabled). Returns either the (possibly new) span to
|
|
871
|
+
* ship, or `null` to drop the span entirely.
|
|
872
|
+
*
|
|
873
|
+
* Ordering: user hook runs first so callers can rewrite the span freely
|
|
874
|
+
* (rename attrs, add new ones, scrub things the default doesn't know
|
|
875
|
+
* about). The default redactor then runs on whatever the user produced,
|
|
876
|
+
* acting as the always-on floor for documented BYOK secrets. If the user
|
|
877
|
+
* sets `disableDefaultRedaction: true`, the floor is skipped.
|
|
878
|
+
*
|
|
879
|
+
* Fail-closed: if the user hook throws, the span is dropped — a buggy
|
|
880
|
+
* hook can never accidentally ship raw, un-redacted spans.
|
|
881
|
+
*/
|
|
882
|
+
redactSpan(span) {
|
|
883
|
+
let current = span;
|
|
884
|
+
if (this.transformSpanHook) {
|
|
885
|
+
try {
|
|
886
|
+
const result = this.transformSpanHook(current);
|
|
887
|
+
if (result === null) return null;
|
|
888
|
+
if (result !== void 0) current = result;
|
|
889
|
+
} catch (err) {
|
|
890
|
+
if (this.debug) {
|
|
891
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
892
|
+
console.warn(`${this.prefix} transformSpan hook threw: ${msg}`);
|
|
893
|
+
}
|
|
894
|
+
return null;
|
|
476
895
|
}
|
|
477
896
|
}
|
|
897
|
+
if (!this.disableDefaultRedaction) {
|
|
898
|
+
current = defaultTransformSpan(current);
|
|
899
|
+
}
|
|
900
|
+
return this.capSpanAttributes(current);
|
|
478
901
|
}
|
|
479
902
|
isDebugEnabled() {
|
|
480
903
|
return this.debug;
|
|
@@ -482,6 +905,9 @@ var TraceShipper = class {
|
|
|
482
905
|
authHeaders() {
|
|
483
906
|
return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
|
|
484
907
|
}
|
|
908
|
+
requestHeaders() {
|
|
909
|
+
return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
|
|
910
|
+
}
|
|
485
911
|
startSpan(args) {
|
|
486
912
|
var _a, _b;
|
|
487
913
|
const ids = createSpanIds(args.parent);
|
|
@@ -492,8 +918,8 @@ var TraceShipper = class {
|
|
|
492
918
|
];
|
|
493
919
|
if ((_b = args.attributes) == null ? void 0 : _b.length) attrs.push(...args.attributes);
|
|
494
920
|
const span = { ids, name: args.name, startTimeUnixNano: started, attributes: attrs };
|
|
495
|
-
|
|
496
|
-
|
|
921
|
+
this.mirrorToLocalDebugger(
|
|
922
|
+
buildOtlpSpan({
|
|
497
923
|
ids: span.ids,
|
|
498
924
|
name: span.name,
|
|
499
925
|
startTimeUnixNano: span.startTimeUnixNano,
|
|
@@ -501,16 +927,21 @@ var TraceShipper = class {
|
|
|
501
927
|
// placeholder — will be updated on endSpan
|
|
502
928
|
attributes: span.attributes,
|
|
503
929
|
status: { code: SpanStatusCode.UNSET }
|
|
504
|
-
})
|
|
505
|
-
|
|
506
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
507
|
-
baseUrl: this.localDebuggerUrl,
|
|
508
|
-
debug: false,
|
|
509
|
-
sdkName: this.sdkName
|
|
510
|
-
});
|
|
511
|
-
}
|
|
930
|
+
})
|
|
931
|
+
);
|
|
512
932
|
return span;
|
|
513
933
|
}
|
|
934
|
+
mirrorToLocalDebugger(span) {
|
|
935
|
+
if (!this.localDebuggerUrl) return;
|
|
936
|
+
const redacted = this.redactSpan(span);
|
|
937
|
+
if (redacted === null) return;
|
|
938
|
+
const body = buildExportTraceServiceRequest([redacted], this.serviceName, this.serviceVersion);
|
|
939
|
+
mirrorTraceExportToLocalDebugger(body, {
|
|
940
|
+
baseUrl: this.localDebuggerUrl,
|
|
941
|
+
debug: false,
|
|
942
|
+
sdkName: this.sdkName
|
|
943
|
+
});
|
|
944
|
+
}
|
|
514
945
|
endSpan(span, extra) {
|
|
515
946
|
var _a, _b;
|
|
516
947
|
if (span.endTimeUnixNano) return;
|
|
@@ -532,14 +963,7 @@ var TraceShipper = class {
|
|
|
532
963
|
status
|
|
533
964
|
});
|
|
534
965
|
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
|
-
}
|
|
966
|
+
this.mirrorToLocalDebugger(otlp);
|
|
543
967
|
}
|
|
544
968
|
createSpan(args) {
|
|
545
969
|
var _a;
|
|
@@ -557,14 +981,7 @@ var TraceShipper = class {
|
|
|
557
981
|
status: args.status
|
|
558
982
|
});
|
|
559
983
|
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
|
-
}
|
|
984
|
+
this.mirrorToLocalDebugger(otlp);
|
|
568
985
|
}
|
|
569
986
|
enqueue(span) {
|
|
570
987
|
if (!this.enabled) return;
|
|
@@ -576,10 +993,12 @@ var TraceShipper = class {
|
|
|
576
993
|
)}`
|
|
577
994
|
);
|
|
578
995
|
}
|
|
996
|
+
const redacted = this.redactSpan(span);
|
|
997
|
+
if (redacted === null) return;
|
|
579
998
|
if (this.queue.length >= this.maxQueueSize) {
|
|
580
999
|
this.queue.shift();
|
|
581
1000
|
}
|
|
582
|
-
this.queue.push(
|
|
1001
|
+
this.queue.push(redacted);
|
|
583
1002
|
if (this.queue.length >= this.maxBatchSize) {
|
|
584
1003
|
void this.flush().catch(() => {
|
|
585
1004
|
});
|
|
@@ -601,6 +1020,17 @@ var TraceShipper = class {
|
|
|
601
1020
|
}
|
|
602
1021
|
while (this.queue.length > 0) {
|
|
603
1022
|
const batch = this.queue.splice(0, this.maxBatchSize);
|
|
1023
|
+
if (!this.writeKey) continue;
|
|
1024
|
+
const opts = this.requestOpts();
|
|
1025
|
+
if (!opts) {
|
|
1026
|
+
rateLimitedLog(
|
|
1027
|
+
`${this.prefix}.shutdown_deadline`,
|
|
1028
|
+
() => console.warn(
|
|
1029
|
+
`${this.prefix} shutdown flush deadline exceeded; dropping ${batch.length} spans`
|
|
1030
|
+
)
|
|
1031
|
+
);
|
|
1032
|
+
continue;
|
|
1033
|
+
}
|
|
604
1034
|
const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
|
|
605
1035
|
const url = `${this.baseUrl}traces`;
|
|
606
1036
|
if (this.debug) {
|
|
@@ -609,11 +1039,7 @@ var TraceShipper = class {
|
|
|
609
1039
|
endpoint: url
|
|
610
1040
|
});
|
|
611
1041
|
}
|
|
612
|
-
const p = postJson(url, body, this.
|
|
613
|
-
maxAttempts: 3,
|
|
614
|
-
debug: this.debug,
|
|
615
|
-
sdkName: this.sdkName
|
|
616
|
-
});
|
|
1042
|
+
const p = postJson(url, body, this.requestHeaders(), opts);
|
|
617
1043
|
this.inFlight.add(p);
|
|
618
1044
|
try {
|
|
619
1045
|
try {
|
|
@@ -621,21 +1047,61 @@ var TraceShipper = class {
|
|
|
621
1047
|
if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
|
|
622
1048
|
} catch (err) {
|
|
623
1049
|
const msg = err instanceof Error ? err.message : String(err);
|
|
624
|
-
|
|
1050
|
+
rateLimitedLog(
|
|
1051
|
+
`${this.prefix}.send_spans_failed`,
|
|
1052
|
+
() => console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`)
|
|
1053
|
+
);
|
|
625
1054
|
}
|
|
626
1055
|
} finally {
|
|
627
1056
|
this.inFlight.delete(p);
|
|
628
1057
|
}
|
|
629
1058
|
}
|
|
630
1059
|
}
|
|
1060
|
+
/** See EventShipper.requestOpts — same shutdown-budget semantics. */
|
|
1061
|
+
requestOpts() {
|
|
1062
|
+
if (this.shutdownDeadlineAt !== void 0) {
|
|
1063
|
+
const remainingMs = this.shutdownDeadlineAt - Date.now();
|
|
1064
|
+
if (remainingMs <= 0) return null;
|
|
1065
|
+
return {
|
|
1066
|
+
maxAttempts: 1,
|
|
1067
|
+
debug: this.debug,
|
|
1068
|
+
sdkName: this.sdkName,
|
|
1069
|
+
timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
if (this.hasShutdown) {
|
|
1073
|
+
return {
|
|
1074
|
+
maxAttempts: 1,
|
|
1075
|
+
debug: this.debug,
|
|
1076
|
+
sdkName: this.sdkName,
|
|
1077
|
+
timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
|
|
1081
|
+
}
|
|
631
1082
|
async shutdown() {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
1083
|
+
this.hasShutdown = true;
|
|
1084
|
+
this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
|
|
1085
|
+
try {
|
|
1086
|
+
if (this.timer) {
|
|
1087
|
+
clearTimeout(this.timer);
|
|
1088
|
+
this.timer = void 0;
|
|
1089
|
+
}
|
|
1090
|
+
const drain = async () => {
|
|
1091
|
+
await this.flush();
|
|
1092
|
+
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
1093
|
+
})));
|
|
1094
|
+
};
|
|
1095
|
+
const settled = await raceWithTimeout(drain(), SHUTDOWN_DEADLINE_MS);
|
|
1096
|
+
if (!settled) {
|
|
1097
|
+
rateLimitedLog(
|
|
1098
|
+
`${this.prefix}.shutdown_deadline`,
|
|
1099
|
+
() => console.warn(`${this.prefix} shutdown flush deadline exceeded; abandoning in-flight spans`)
|
|
1100
|
+
);
|
|
1101
|
+
}
|
|
1102
|
+
} finally {
|
|
1103
|
+
this.shutdownDeadlineAt = void 0;
|
|
635
1104
|
}
|
|
636
|
-
await this.flush();
|
|
637
|
-
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
638
|
-
})));
|
|
639
1105
|
}
|
|
640
1106
|
};
|
|
641
1107
|
|
|
@@ -688,7 +1154,7 @@ import { homedir, userInfo } from "os";
|
|
|
688
1154
|
import { dirname, join } from "path";
|
|
689
1155
|
var CONFIG_PATH = join(homedir(), ".config", "raindrop", "config.json");
|
|
690
1156
|
function loadConfig() {
|
|
691
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
1157
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
|
692
1158
|
let file = {};
|
|
693
1159
|
try {
|
|
694
1160
|
if (existsSync(CONFIG_PATH)) {
|
|
@@ -728,9 +1194,10 @@ function loadConfig() {
|
|
|
728
1194
|
return {
|
|
729
1195
|
writeKey: (_c = (_b = process.env["RAINDROP_WRITE_KEY"]) != null ? _b : file.write_key) != null ? _c : "",
|
|
730
1196
|
endpoint: (_e = (_d = process.env["RAINDROP_API_URL"]) != null ? _d : file.api_url) != null ? _e : "https://api.raindrop.ai/v1",
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
1197
|
+
projectId: (_f = process.env["RAINDROP_PROJECT_ID"]) != null ? _f : file.project_id,
|
|
1198
|
+
userId: (_h = (_g = process.env["RAINDROP_USER_ID"]) != null ? _g : file.user_id) != null ? _h : systemUser,
|
|
1199
|
+
convoId: ((_i = process.env["RAINDROP_CONVO_ID"]) == null ? void 0 : _i.trim()) || void 0,
|
|
1200
|
+
debug: process.env["RAINDROP_DEBUG"] === "true" ? true : (_j = file.debug) != null ? _j : false,
|
|
734
1201
|
enabled: (() => {
|
|
735
1202
|
var _a2;
|
|
736
1203
|
const env = process.env["RAINDROP_ENABLED"];
|
|
@@ -738,7 +1205,7 @@ function loadConfig() {
|
|
|
738
1205
|
const lower = env.toLowerCase();
|
|
739
1206
|
return lower !== "false" && env !== "0";
|
|
740
1207
|
})(),
|
|
741
|
-
eventName: (
|
|
1208
|
+
eventName: (_l = (_k = process.env["RAINDROP_EVENT_NAME"]) != null ? _k : file.event_name) != null ? _l : "claude_code_session",
|
|
742
1209
|
customProperties,
|
|
743
1210
|
selfDiagnostics
|
|
744
1211
|
};
|
|
@@ -984,18 +1451,85 @@ function transcriptToProperties(summary) {
|
|
|
984
1451
|
|
|
985
1452
|
// src/event-mapper.ts
|
|
986
1453
|
var MAX_ATTR_LENGTH = 32768;
|
|
1454
|
+
var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
|
|
1455
|
+
var MAX_TEXT_FIELD_CHARS = 1e6;
|
|
1456
|
+
var MAX_BOUNDED_DEPTH = 12;
|
|
987
1457
|
function truncate(value) {
|
|
988
1458
|
if (value === void 0) return void 0;
|
|
989
1459
|
if (value.length <= MAX_ATTR_LENGTH) return value;
|
|
990
1460
|
const suffix = "\n...[truncated]";
|
|
991
1461
|
return value.slice(0, MAX_ATTR_LENGTH - suffix.length) + suffix;
|
|
992
1462
|
}
|
|
1463
|
+
function capText2(value, limit = MAX_TEXT_FIELD_CHARS) {
|
|
1464
|
+
if (value.length <= limit) return value;
|
|
1465
|
+
if (limit > TRUNCATION_MARKER2.length) {
|
|
1466
|
+
return value.slice(0, limit - TRUNCATION_MARKER2.length) + TRUNCATION_MARKER2;
|
|
1467
|
+
}
|
|
1468
|
+
return value.slice(0, limit);
|
|
1469
|
+
}
|
|
1470
|
+
function boundedClone2(value, budget, depth) {
|
|
1471
|
+
if (budget.remaining <= 0) return TRUNCATION_MARKER2;
|
|
1472
|
+
if (typeof value === "string") {
|
|
1473
|
+
if (value.length > budget.remaining) {
|
|
1474
|
+
const taken = value.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
|
|
1475
|
+
budget.remaining = 0;
|
|
1476
|
+
return taken;
|
|
1477
|
+
}
|
|
1478
|
+
budget.remaining -= Math.max(value.length, 1);
|
|
1479
|
+
return value;
|
|
1480
|
+
}
|
|
1481
|
+
if (value === null || typeof value === "number" || typeof value === "boolean") {
|
|
1482
|
+
budget.remaining -= 8;
|
|
1483
|
+
return value;
|
|
1484
|
+
}
|
|
1485
|
+
if (typeof value !== "object") {
|
|
1486
|
+
budget.remaining -= 8;
|
|
1487
|
+
return value;
|
|
1488
|
+
}
|
|
1489
|
+
if (depth >= MAX_BOUNDED_DEPTH) {
|
|
1490
|
+
budget.remaining -= 16;
|
|
1491
|
+
return `<max depth: ${TRUNCATION_MARKER2}>`;
|
|
1492
|
+
}
|
|
1493
|
+
const withToJson = value;
|
|
1494
|
+
if (typeof withToJson.toJSON === "function") {
|
|
1495
|
+
try {
|
|
1496
|
+
return boundedClone2(withToJson.toJSON(), budget, depth + 1);
|
|
1497
|
+
} catch (e) {
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
if (Array.isArray(value)) {
|
|
1501
|
+
const out2 = [];
|
|
1502
|
+
for (const item of value) {
|
|
1503
|
+
if (budget.remaining <= 0) {
|
|
1504
|
+
out2.push(TRUNCATION_MARKER2);
|
|
1505
|
+
break;
|
|
1506
|
+
}
|
|
1507
|
+
out2.push(boundedClone2(item, budget, depth + 1));
|
|
1508
|
+
}
|
|
1509
|
+
return out2;
|
|
1510
|
+
}
|
|
1511
|
+
const out = {};
|
|
1512
|
+
for (const key of Object.keys(value)) {
|
|
1513
|
+
if (budget.remaining <= 0) {
|
|
1514
|
+
out["..."] = TRUNCATION_MARKER2;
|
|
1515
|
+
break;
|
|
1516
|
+
}
|
|
1517
|
+
budget.remaining -= Math.max(key.length, 1);
|
|
1518
|
+
out[key] = boundedClone2(value[key], budget, depth + 1);
|
|
1519
|
+
}
|
|
1520
|
+
return out;
|
|
1521
|
+
}
|
|
993
1522
|
function safeStringify(value) {
|
|
994
1523
|
if (value === void 0 || value === null) return void 0;
|
|
995
1524
|
try {
|
|
996
|
-
|
|
1525
|
+
const pruned = boundedClone2(
|
|
1526
|
+
value,
|
|
1527
|
+
{ remaining: MAX_ATTR_LENGTH + TRUNCATION_MARKER2.length + 256 },
|
|
1528
|
+
0
|
|
1529
|
+
);
|
|
1530
|
+
return truncate(JSON.stringify(pruned));
|
|
997
1531
|
} catch (e) {
|
|
998
|
-
return truncate(String(value));
|
|
1532
|
+
return truncate(capText2(String(value), MAX_ATTR_LENGTH + 16));
|
|
999
1533
|
}
|
|
1000
1534
|
}
|
|
1001
1535
|
var STATE_DIR = join2(tmpdir(), "raindrop-claude-code");
|
|
@@ -1176,7 +1710,9 @@ async function handleUserPromptSubmit(payload, convoId, config, properties, even
|
|
|
1176
1710
|
userId: config.userId,
|
|
1177
1711
|
convoId,
|
|
1178
1712
|
eventName: config.eventName,
|
|
1179
|
-
|
|
1713
|
+
// Capped so a pasted multi-MB prompt can't produce an event that the
|
|
1714
|
+
// ingest API rejects (silent data loss) — it lands truncated instead.
|
|
1715
|
+
input: payload.prompt !== void 0 ? capText2(payload.prompt) : void 0,
|
|
1180
1716
|
model,
|
|
1181
1717
|
properties
|
|
1182
1718
|
};
|
|
@@ -1299,7 +1835,7 @@ async function handlePostCompact(payload, eventId, convoId, config, properties,
|
|
|
1299
1835
|
properties: {
|
|
1300
1836
|
...properties,
|
|
1301
1837
|
compaction_trigger: payload.trigger,
|
|
1302
|
-
compact_summary: payload.compact_summary
|
|
1838
|
+
compact_summary: payload.compact_summary !== void 0 ? capText2(payload.compact_summary) : void 0
|
|
1303
1839
|
}
|
|
1304
1840
|
});
|
|
1305
1841
|
}
|
|
@@ -1534,7 +2070,7 @@ function enrichFromTranscript(payload, eventId, traceShipper) {
|
|
|
1534
2070
|
attrInt("gen_ai.usage.prompt_tokens", phase.inputTokens),
|
|
1535
2071
|
attrInt("gen_ai.usage.completion_tokens", phase.outputTokens),
|
|
1536
2072
|
...phase.cacheReadTokens > 0 ? [attrInt("gen_ai.usage.cache_read_tokens", phase.cacheReadTokens)] : [],
|
|
1537
|
-
attrString("ai.response.text", outputText),
|
|
2073
|
+
attrString("ai.response.text", truncate(outputText)),
|
|
1538
2074
|
...toolCallNames ? [attrString("ai.toolCall.names", toolCallNames)] : [],
|
|
1539
2075
|
...phase.hasThinking ? [attrString("ai.has_thinking", "true")] : [],
|
|
1540
2076
|
attrInt("ai.llm_call.index", emittedPhaseIndex)
|
|
@@ -1594,7 +2130,7 @@ async function handleStopOrFailure(payload, eventId, convoId, config, properties
|
|
|
1594
2130
|
userId: config.userId,
|
|
1595
2131
|
convoId,
|
|
1596
2132
|
eventName: config.eventName,
|
|
1597
|
-
output,
|
|
2133
|
+
output: output !== void 0 ? capText2(output) : void 0,
|
|
1598
2134
|
...(summary == null ? void 0 : summary.model) ? { model: summary.model } : {},
|
|
1599
2135
|
properties: {
|
|
1600
2136
|
...properties,
|
|
@@ -1625,6 +2161,7 @@ import { join as join3 } from "path";
|
|
|
1625
2161
|
var DEFAULT_PORT = 5899;
|
|
1626
2162
|
var DEFAULT_URL = `http://localhost:${DEFAULT_PORT}/v1/`;
|
|
1627
2163
|
var HEALTH_TIMEOUT_MS = 300;
|
|
2164
|
+
var MIRROR_TIMEOUT_MS = 2e3;
|
|
1628
2165
|
var CACHE_DIR = join3(tmpdir2(), "raindrop-claude-code");
|
|
1629
2166
|
var CACHE_FILE = join3(CACHE_DIR, "debugger_cache");
|
|
1630
2167
|
var CACHE_TTL_MS = 5e3;
|
|
@@ -1648,10 +2185,8 @@ function writeCache(url) {
|
|
|
1648
2185
|
async function healthPing(baseUrl) {
|
|
1649
2186
|
const healthUrl = baseUrl.replace(/\/v1\/$/, "/health");
|
|
1650
2187
|
try {
|
|
1651
|
-
const
|
|
1652
|
-
const
|
|
1653
|
-
const resp = await fetch(healthUrl, { signal: controller.signal });
|
|
1654
|
-
clearTimeout(timeout);
|
|
2188
|
+
const signal = AbortSignal.timeout(HEALTH_TIMEOUT_MS);
|
|
2189
|
+
const resp = await fetch(healthUrl, { signal });
|
|
1655
2190
|
if (resp.ok) {
|
|
1656
2191
|
const body = await resp.json();
|
|
1657
2192
|
if (body.ok) return baseUrl;
|
|
@@ -1694,7 +2229,8 @@ function mirrorEventToLocalDebugger(baseUrl, payload, debug) {
|
|
|
1694
2229
|
void fetch(url, {
|
|
1695
2230
|
method: "POST",
|
|
1696
2231
|
headers: { "Content-Type": "application/json" },
|
|
1697
|
-
body: JSON.stringify(payload)
|
|
2232
|
+
body: JSON.stringify(payload),
|
|
2233
|
+
signal: AbortSignal.timeout(MIRROR_TIMEOUT_MS)
|
|
1698
2234
|
}).catch(() => {
|
|
1699
2235
|
});
|
|
1700
2236
|
}
|
|
@@ -1835,6 +2371,7 @@ async function executeTool(args) {
|
|
|
1835
2371
|
writeKey: config.writeKey,
|
|
1836
2372
|
endpoint: config.endpoint,
|
|
1837
2373
|
debug: false,
|
|
2374
|
+
projectId: config.projectId,
|
|
1838
2375
|
enabled: true
|
|
1839
2376
|
});
|
|
1840
2377
|
try {
|