@raindrop-ai/langchain 0.0.3 → 0.0.5
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/README.md +2 -1
- package/dist/index.d.mts +189 -2
- package/dist/index.d.ts +189 -2
- package/dist/index.js +695 -102
- package/dist/index.mjs +695 -102
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -28,7 +28,7 @@ module.exports = __toCommonJS(index_exports);
|
|
|
28
28
|
// src/callback-handler.ts
|
|
29
29
|
var import_base = require("@langchain/core/callbacks/base");
|
|
30
30
|
|
|
31
|
-
// ../core/dist/chunk-
|
|
31
|
+
// ../core/dist/chunk-SK6EJEO7.js
|
|
32
32
|
function getCrypto() {
|
|
33
33
|
const c = globalThis.crypto;
|
|
34
34
|
return c;
|
|
@@ -83,6 +83,8 @@ function base64Encode(bytes) {
|
|
|
83
83
|
}
|
|
84
84
|
return out;
|
|
85
85
|
}
|
|
86
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
87
|
+
var MAX_RETRY_DELAY_MS = 3e4;
|
|
86
88
|
function wait(ms) {
|
|
87
89
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
88
90
|
}
|
|
@@ -90,6 +92,46 @@ function formatEndpoint(endpoint) {
|
|
|
90
92
|
if (!endpoint) return void 0;
|
|
91
93
|
return endpoint.endsWith("/") ? endpoint : `${endpoint}/`;
|
|
92
94
|
}
|
|
95
|
+
function redactUrlForLog(url) {
|
|
96
|
+
try {
|
|
97
|
+
const parsed = new URL(url);
|
|
98
|
+
parsed.username = "";
|
|
99
|
+
parsed.password = "";
|
|
100
|
+
parsed.search = "";
|
|
101
|
+
parsed.hash = "";
|
|
102
|
+
return parsed.toString();
|
|
103
|
+
} catch (e) {
|
|
104
|
+
return "<unparseable-url>";
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
var RATE_LIMITED_LOG_INTERVAL_MS = 3e4;
|
|
108
|
+
var rateLimitedLogLast = /* @__PURE__ */ new Map();
|
|
109
|
+
function rateLimitedLog(key, log) {
|
|
110
|
+
const now = Date.now();
|
|
111
|
+
const last = rateLimitedLogLast.get(key);
|
|
112
|
+
if (last !== void 0 && now - last < RATE_LIMITED_LOG_INTERVAL_MS) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
rateLimitedLogLast.set(key, now);
|
|
116
|
+
log();
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
async function raceWithTimeout(promise, timeoutMs) {
|
|
120
|
+
let timer;
|
|
121
|
+
const settledInTime = await Promise.race([
|
|
122
|
+
promise.then(
|
|
123
|
+
() => true,
|
|
124
|
+
() => true
|
|
125
|
+
),
|
|
126
|
+
new Promise((resolve) => {
|
|
127
|
+
var _a;
|
|
128
|
+
timer = setTimeout(() => resolve(false), Math.max(0, timeoutMs));
|
|
129
|
+
(_a = timer.unref) == null ? void 0 : _a.call(timer);
|
|
130
|
+
})
|
|
131
|
+
]);
|
|
132
|
+
if (timer) clearTimeout(timer);
|
|
133
|
+
return settledInTime;
|
|
134
|
+
}
|
|
93
135
|
function parseRetryAfter(headers) {
|
|
94
136
|
var _a;
|
|
95
137
|
const value = (_a = headers.get("Retry-After")) != null ? _a : headers.get("retry-after");
|
|
@@ -106,12 +148,12 @@ function parseRetryAfter(headers) {
|
|
|
106
148
|
function getRetryDelayMs(attemptNumber, previousError) {
|
|
107
149
|
if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
|
|
108
150
|
const v = previousError.retryAfterMs;
|
|
109
|
-
if (typeof v === "number") return Math.max(0, v);
|
|
151
|
+
if (typeof v === "number") return Math.min(Math.max(0, v), MAX_RETRY_DELAY_MS);
|
|
110
152
|
}
|
|
111
153
|
if (attemptNumber <= 1) return 0;
|
|
112
154
|
const base = 500;
|
|
113
155
|
const factor = Math.pow(2, attemptNumber - 2);
|
|
114
|
-
return base * factor;
|
|
156
|
+
return Math.min(base * factor, MAX_RETRY_DELAY_MS);
|
|
115
157
|
}
|
|
116
158
|
async function withRetry(operation, opName, opts) {
|
|
117
159
|
const prefix = opts.sdkName ? `[raindrop-ai/${opts.sdkName}]` : "[raindrop-ai/core]";
|
|
@@ -146,7 +188,9 @@ async function withRetry(operation, opName, opts) {
|
|
|
146
188
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
147
189
|
}
|
|
148
190
|
async function postJson(url, body, headers, opts) {
|
|
149
|
-
|
|
191
|
+
var _a;
|
|
192
|
+
const opName = `POST ${redactUrlForLog(url)}`;
|
|
193
|
+
const timeoutMs = (_a = opts.timeoutMs) != null ? _a : DEFAULT_REQUEST_TIMEOUT_MS;
|
|
150
194
|
await withRetry(
|
|
151
195
|
async () => {
|
|
152
196
|
const resp = await fetch(url, {
|
|
@@ -155,7 +199,8 @@ async function postJson(url, body, headers, opts) {
|
|
|
155
199
|
"Content-Type": "application/json",
|
|
156
200
|
...headers
|
|
157
201
|
},
|
|
158
|
-
body: JSON.stringify(body)
|
|
202
|
+
body: JSON.stringify(body),
|
|
203
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
159
204
|
});
|
|
160
205
|
if (!resp.ok) {
|
|
161
206
|
const text = await resp.text().catch(() => "");
|
|
@@ -172,6 +217,27 @@ async function postJson(url, body, headers, opts) {
|
|
|
172
217
|
opts
|
|
173
218
|
);
|
|
174
219
|
}
|
|
220
|
+
var DEFAULT_MAX_TEXT_FIELD_CHARS = 1e6;
|
|
221
|
+
var TRUNCATION_MARKER = "...[truncated by raindrop]";
|
|
222
|
+
var currentDefaultMaxTextFieldChars = DEFAULT_MAX_TEXT_FIELD_CHARS;
|
|
223
|
+
function resolveMaxTextFieldChars(value) {
|
|
224
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
225
|
+
return Math.floor(value);
|
|
226
|
+
}
|
|
227
|
+
return currentDefaultMaxTextFieldChars;
|
|
228
|
+
}
|
|
229
|
+
function truncateToLimit(text, limit) {
|
|
230
|
+
if (limit > TRUNCATION_MARKER.length) {
|
|
231
|
+
return text.slice(0, limit - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;
|
|
232
|
+
}
|
|
233
|
+
return text.slice(0, Math.max(0, limit));
|
|
234
|
+
}
|
|
235
|
+
function capText(value, limit) {
|
|
236
|
+
if (typeof value !== "string") return value;
|
|
237
|
+
const max = limit != null ? limit : currentDefaultMaxTextFieldChars;
|
|
238
|
+
if (value.length <= max) return value;
|
|
239
|
+
return truncateToLimit(value, max);
|
|
240
|
+
}
|
|
175
241
|
var SpanStatusCode = {
|
|
176
242
|
UNSET: 0,
|
|
177
243
|
OK: 1,
|
|
@@ -229,6 +295,112 @@ function buildExportTraceServiceRequest(spans, serviceName = "raindrop.core", se
|
|
|
229
295
|
]
|
|
230
296
|
};
|
|
231
297
|
}
|
|
298
|
+
var LOCAL_DEBUGGER_ENV_VAR = "RAINDROP_LOCAL_DEBUGGER";
|
|
299
|
+
var WORKSHOP_ENV_VAR = "RAINDROP_WORKSHOP";
|
|
300
|
+
var DEFAULT_LOCAL_WORKSHOP_URL = "http://localhost:5899/v1/";
|
|
301
|
+
function readEnvVar(name) {
|
|
302
|
+
var _a;
|
|
303
|
+
try {
|
|
304
|
+
const env = (_a = globalThis == null ? void 0 : globalThis.process) == null ? void 0 : _a.env;
|
|
305
|
+
if (env && typeof env[name] === "string" && env[name].length > 0) {
|
|
306
|
+
return env[name];
|
|
307
|
+
}
|
|
308
|
+
} catch (e) {
|
|
309
|
+
}
|
|
310
|
+
return void 0;
|
|
311
|
+
}
|
|
312
|
+
function readWorkshopEnv() {
|
|
313
|
+
const raw = readEnvVar(WORKSHOP_ENV_VAR);
|
|
314
|
+
if (raw === void 0) return void 0;
|
|
315
|
+
const trimmed = raw.trim();
|
|
316
|
+
if (trimmed.length === 0) return void 0;
|
|
317
|
+
if (/^https?:\/\//i.test(trimmed)) return { url: trimmed };
|
|
318
|
+
if (/^(1|true|yes|on)$/i.test(trimmed)) return "enable";
|
|
319
|
+
if (/^(0|false|no|off)$/i.test(trimmed)) return "disable";
|
|
320
|
+
return void 0;
|
|
321
|
+
}
|
|
322
|
+
function isLocalDevHost(hostname) {
|
|
323
|
+
if (!hostname) return false;
|
|
324
|
+
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "0.0.0.0" || hostname === "::1") {
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
if (hostname.endsWith(".localhost")) return true;
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
function readRuntimeHostname() {
|
|
331
|
+
try {
|
|
332
|
+
const loc = globalThis == null ? void 0 : globalThis.location;
|
|
333
|
+
if (loc && typeof loc.hostname === "string" && loc.hostname.length > 0) {
|
|
334
|
+
return loc.hostname;
|
|
335
|
+
}
|
|
336
|
+
} catch (e) {
|
|
337
|
+
}
|
|
338
|
+
return void 0;
|
|
339
|
+
}
|
|
340
|
+
function shouldAutoEnableLocalWorkshop() {
|
|
341
|
+
if (isLocalDevHost(readRuntimeHostname())) return true;
|
|
342
|
+
if (readEnvVar("NODE_ENV") === "development") return true;
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
function resolveLocalDebuggerBaseUrl(baseUrl) {
|
|
346
|
+
var _a, _b, _c;
|
|
347
|
+
if (baseUrl === null) return null;
|
|
348
|
+
if (typeof baseUrl === "string" && baseUrl.length > 0) {
|
|
349
|
+
return (_a = formatEndpoint(baseUrl)) != null ? _a : null;
|
|
350
|
+
}
|
|
351
|
+
const explicitUrlEnv = readEnvVar(LOCAL_DEBUGGER_ENV_VAR);
|
|
352
|
+
if (explicitUrlEnv) return (_b = formatEndpoint(explicitUrlEnv)) != null ? _b : null;
|
|
353
|
+
const workshopEnv = readWorkshopEnv();
|
|
354
|
+
if (workshopEnv === "disable") return null;
|
|
355
|
+
if (workshopEnv === "enable") return DEFAULT_LOCAL_WORKSHOP_URL;
|
|
356
|
+
if (workshopEnv && "url" in workshopEnv) return (_c = formatEndpoint(workshopEnv.url)) != null ? _c : null;
|
|
357
|
+
if (shouldAutoEnableLocalWorkshop()) return DEFAULT_LOCAL_WORKSHOP_URL;
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
function mirrorTraceExportToLocalDebugger(body, options = {}) {
|
|
361
|
+
var _a;
|
|
362
|
+
const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
|
|
363
|
+
if (!baseUrl) return;
|
|
364
|
+
void postJson(`${baseUrl}traces`, body, {}, {
|
|
365
|
+
maxAttempts: 1,
|
|
366
|
+
debug: (_a = options.debug) != null ? _a : false,
|
|
367
|
+
sdkName: options.sdkName
|
|
368
|
+
}).catch(() => {
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
function mirrorPartialEventToLocalDebugger(event, options = {}) {
|
|
372
|
+
var _a;
|
|
373
|
+
const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
|
|
374
|
+
if (!baseUrl) return;
|
|
375
|
+
const headers = options.writeKey ? { Authorization: `Bearer ${options.writeKey}` } : {};
|
|
376
|
+
void postJson(`${baseUrl}events/track_partial`, event, headers, {
|
|
377
|
+
maxAttempts: 1,
|
|
378
|
+
debug: (_a = options.debug) != null ? _a : false,
|
|
379
|
+
sdkName: options.sdkName
|
|
380
|
+
}).catch(() => {
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
var PROJECT_ID_HEADER = "X-Raindrop-Project-Id";
|
|
384
|
+
var PROJECT_ID_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
385
|
+
function isValidProjectIdSlug(value) {
|
|
386
|
+
return PROJECT_ID_SLUG_PATTERN.test(value);
|
|
387
|
+
}
|
|
388
|
+
function normalizeProjectId(raw, opts) {
|
|
389
|
+
if (typeof raw !== "string") return void 0;
|
|
390
|
+
const trimmed = raw.trim();
|
|
391
|
+
if (!trimmed) return void 0;
|
|
392
|
+
if (!isValidProjectIdSlug(trimmed) && opts.debug) {
|
|
393
|
+
console.warn(
|
|
394
|
+
`${opts.prefix} projectId "${trimmed}" does not match slug ${PROJECT_ID_SLUG_PATTERN.source}; sending anyway \u2014 backend may reject with HTTP 400`
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
return trimmed;
|
|
398
|
+
}
|
|
399
|
+
function projectIdHeaders(projectId) {
|
|
400
|
+
return projectId ? { [PROJECT_ID_HEADER]: projectId } : {};
|
|
401
|
+
}
|
|
402
|
+
var SHUTDOWN_DEADLINE_MS = 1e4;
|
|
403
|
+
var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
232
404
|
function mergePatches(target, source) {
|
|
233
405
|
var _a, _b, _c, _d;
|
|
234
406
|
const out = { ...target, ...source };
|
|
@@ -246,7 +418,8 @@ var EventShipper = class {
|
|
|
246
418
|
this.sticky = /* @__PURE__ */ new Map();
|
|
247
419
|
this.timers = /* @__PURE__ */ new Map();
|
|
248
420
|
this.inFlight = /* @__PURE__ */ new Set();
|
|
249
|
-
|
|
421
|
+
this.hasShutdown = false;
|
|
422
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
250
423
|
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
251
424
|
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
252
425
|
this.enabled = opts.enabled !== false;
|
|
@@ -255,11 +428,20 @@ var EventShipper = class {
|
|
|
255
428
|
this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
|
|
256
429
|
this.prefix = `[raindrop-ai/${this.sdkName}]`;
|
|
257
430
|
this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
|
|
431
|
+
this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
|
|
432
|
+
this.localDebuggerUrl = (_f = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _f : void 0;
|
|
433
|
+
if (this.debug && this.localDebuggerUrl) {
|
|
434
|
+
console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
|
|
435
|
+
}
|
|
436
|
+
this.projectId = normalizeProjectId(opts.projectId, {
|
|
437
|
+
debug: this.debug,
|
|
438
|
+
prefix: this.prefix
|
|
439
|
+
});
|
|
258
440
|
const isNode = typeof process !== "undefined" && typeof process.version === "string";
|
|
259
441
|
this.context = {
|
|
260
442
|
library: {
|
|
261
|
-
name: (
|
|
262
|
-
version: (
|
|
443
|
+
name: (_g = opts.libraryName) != null ? _g : "@raindrop-ai/core",
|
|
444
|
+
version: (_h = opts.libraryVersion) != null ? _h : "0.0.0"
|
|
263
445
|
},
|
|
264
446
|
metadata: {
|
|
265
447
|
jsRuntime: isNode ? "node" : "web",
|
|
@@ -273,10 +455,55 @@ var EventShipper = class {
|
|
|
273
455
|
authHeaders() {
|
|
274
456
|
return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
|
|
275
457
|
}
|
|
458
|
+
requestHeaders() {
|
|
459
|
+
return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Build the retry/timeout options for one POST, honoring the shutdown
|
|
463
|
+
* deadline. Returns `null` when the shutdown drain window is exhausted —
|
|
464
|
+
* the caller must drop the payload (with a rate-limited warning) instead
|
|
465
|
+
* of issuing a request that could outlive process exit.
|
|
466
|
+
*
|
|
467
|
+
* Checked fresh on EVERY send, so a shutdown that begins while the flush
|
|
468
|
+
* path is mid-drain takes effect immediately: no further retries, and the
|
|
469
|
+
* per-attempt timeout is clamped to the remaining window. After
|
|
470
|
+
* `shutdown()` returns (deadline cleared, `hasShutdown` still set),
|
|
471
|
+
* sends — late callers, or flush work the deadline abandoned mid-drain —
|
|
472
|
+
* run as a single short attempt rather than regaining the full retry
|
|
473
|
+
* schedule.
|
|
474
|
+
*/
|
|
475
|
+
requestOpts() {
|
|
476
|
+
if (this.shutdownDeadlineAt !== void 0) {
|
|
477
|
+
const remainingMs = this.shutdownDeadlineAt - Date.now();
|
|
478
|
+
if (remainingMs <= 0) return null;
|
|
479
|
+
return {
|
|
480
|
+
maxAttempts: 1,
|
|
481
|
+
debug: this.debug,
|
|
482
|
+
sdkName: this.sdkName,
|
|
483
|
+
timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
if (this.hasShutdown) {
|
|
487
|
+
return {
|
|
488
|
+
maxAttempts: 1,
|
|
489
|
+
debug: this.debug,
|
|
490
|
+
sdkName: this.sdkName,
|
|
491
|
+
timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
|
|
495
|
+
}
|
|
276
496
|
async patch(eventId, patch) {
|
|
277
497
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
278
498
|
if (!this.enabled) return;
|
|
279
499
|
if (!eventId || !eventId.trim()) return;
|
|
500
|
+
const maxChars = resolveMaxTextFieldChars(this.maxTextFieldCharsOpt);
|
|
501
|
+
if (typeof patch.input === "string" && patch.input.length > maxChars) {
|
|
502
|
+
patch = { ...patch, input: capText(patch.input, maxChars) };
|
|
503
|
+
}
|
|
504
|
+
if (typeof patch.output === "string" && patch.output.length > maxChars) {
|
|
505
|
+
patch = { ...patch, output: capText(patch.output, maxChars) };
|
|
506
|
+
}
|
|
280
507
|
if (this.debug) {
|
|
281
508
|
console.log(`${this.prefix} queue patch`, {
|
|
282
509
|
eventId,
|
|
@@ -323,9 +550,16 @@ var EventShipper = class {
|
|
|
323
550
|
})));
|
|
324
551
|
}
|
|
325
552
|
async shutdown() {
|
|
326
|
-
|
|
327
|
-
this.
|
|
328
|
-
|
|
553
|
+
this.hasShutdown = true;
|
|
554
|
+
this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
|
|
555
|
+
try {
|
|
556
|
+
for (const t of this.timers.values()) clearTimeout(t);
|
|
557
|
+
this.timers.clear();
|
|
558
|
+
const settled = await raceWithTimeout(this.flush(), SHUTDOWN_DEADLINE_MS);
|
|
559
|
+
if (!settled) this.warnShutdownDrop("in-flight request(s) at shutdown");
|
|
560
|
+
} finally {
|
|
561
|
+
this.shutdownDeadlineAt = void 0;
|
|
562
|
+
}
|
|
329
563
|
}
|
|
330
564
|
async trackSignal(signal) {
|
|
331
565
|
var _a, _b;
|
|
@@ -345,16 +579,21 @@ var EventShipper = class {
|
|
|
345
579
|
}
|
|
346
580
|
}
|
|
347
581
|
];
|
|
582
|
+
if (!this.writeKey) return;
|
|
348
583
|
const url = `${this.baseUrl}signals/track`;
|
|
584
|
+
const opts = this.requestOpts();
|
|
585
|
+
if (!opts) {
|
|
586
|
+
this.warnShutdownDrop("signal");
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
349
589
|
try {
|
|
350
|
-
await postJson(url, body, this.
|
|
351
|
-
maxAttempts: 3,
|
|
352
|
-
debug: this.debug,
|
|
353
|
-
sdkName: this.sdkName
|
|
354
|
-
});
|
|
590
|
+
await postJson(url, body, this.requestHeaders(), opts);
|
|
355
591
|
} catch (err) {
|
|
356
592
|
const msg = err instanceof Error ? err.message : String(err);
|
|
357
|
-
|
|
593
|
+
rateLimitedLog(
|
|
594
|
+
`${this.prefix}.send_signal_failed`,
|
|
595
|
+
() => console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`)
|
|
596
|
+
);
|
|
358
597
|
}
|
|
359
598
|
}
|
|
360
599
|
async identify(users) {
|
|
@@ -375,19 +614,30 @@ var EventShipper = class {
|
|
|
375
614
|
traits: (_a = user.traits) != null ? _a : {}
|
|
376
615
|
};
|
|
377
616
|
});
|
|
617
|
+
if (!this.writeKey) return;
|
|
378
618
|
if (body.length === 0) return;
|
|
379
619
|
const url = `${this.baseUrl}users/identify`;
|
|
620
|
+
const opts = this.requestOpts();
|
|
621
|
+
if (!opts) {
|
|
622
|
+
this.warnShutdownDrop("identify");
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
380
625
|
try {
|
|
381
|
-
await postJson(url, body, this.
|
|
382
|
-
maxAttempts: 3,
|
|
383
|
-
debug: this.debug,
|
|
384
|
-
sdkName: this.sdkName
|
|
385
|
-
});
|
|
626
|
+
await postJson(url, body, this.requestHeaders(), opts);
|
|
386
627
|
} catch (err) {
|
|
387
628
|
const msg = err instanceof Error ? err.message : String(err);
|
|
388
|
-
|
|
629
|
+
rateLimitedLog(
|
|
630
|
+
`${this.prefix}.send_identify_failed`,
|
|
631
|
+
() => console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`)
|
|
632
|
+
);
|
|
389
633
|
}
|
|
390
634
|
}
|
|
635
|
+
warnShutdownDrop(what) {
|
|
636
|
+
rateLimitedLog(
|
|
637
|
+
`${this.prefix}.shutdown_deadline`,
|
|
638
|
+
() => console.warn(`${this.prefix} shutdown flush deadline exceeded; dropping ${what}`)
|
|
639
|
+
);
|
|
640
|
+
}
|
|
391
641
|
async flushOne(eventId) {
|
|
392
642
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
393
643
|
if (!this.enabled) return;
|
|
@@ -451,11 +701,25 @@ var EventShipper = class {
|
|
|
451
701
|
endpoint: url
|
|
452
702
|
});
|
|
453
703
|
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
704
|
+
if (this.localDebuggerUrl) {
|
|
705
|
+
mirrorPartialEventToLocalDebugger(payload, {
|
|
706
|
+
baseUrl: this.localDebuggerUrl,
|
|
707
|
+
writeKey: this.writeKey,
|
|
708
|
+
debug: this.debug,
|
|
709
|
+
sdkName: this.sdkName
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
if (!this.writeKey) {
|
|
713
|
+
if (!isPending) this.sticky.delete(eventId);
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
const opts = this.requestOpts();
|
|
717
|
+
if (!opts) {
|
|
718
|
+
this.warnShutdownDrop(`track_partial ${eventId}`);
|
|
719
|
+
if (!isPending) this.sticky.delete(eventId);
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
const p = postJson(url, payload, this.requestHeaders(), opts);
|
|
459
723
|
this.inFlight.add(p);
|
|
460
724
|
try {
|
|
461
725
|
try {
|
|
@@ -465,7 +729,10 @@ var EventShipper = class {
|
|
|
465
729
|
}
|
|
466
730
|
} catch (err) {
|
|
467
731
|
const msg = err instanceof Error ? err.message : String(err);
|
|
468
|
-
|
|
732
|
+
rateLimitedLog(
|
|
733
|
+
`${this.prefix}.send_track_partial_failed`,
|
|
734
|
+
() => console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`)
|
|
735
|
+
);
|
|
469
736
|
}
|
|
470
737
|
} finally {
|
|
471
738
|
this.inFlight.delete(p);
|
|
@@ -475,28 +742,114 @@ var EventShipper = class {
|
|
|
475
742
|
}
|
|
476
743
|
}
|
|
477
744
|
};
|
|
478
|
-
var
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
745
|
+
var DEFAULT_SECRET_KEY_NAMES = [
|
|
746
|
+
"apikey",
|
|
747
|
+
"apisecret",
|
|
748
|
+
"apitoken",
|
|
749
|
+
"secretaccesskey",
|
|
750
|
+
"sessiontoken",
|
|
751
|
+
"privatekey",
|
|
752
|
+
"privatekeyid",
|
|
753
|
+
"clientsecret",
|
|
754
|
+
"accesstoken",
|
|
755
|
+
"refreshtoken",
|
|
756
|
+
"oauthtoken",
|
|
757
|
+
"bearertoken",
|
|
758
|
+
"authorization",
|
|
759
|
+
"password",
|
|
760
|
+
"passphrase"
|
|
761
|
+
];
|
|
762
|
+
var REDACTED_PLACEHOLDER = "[REDACTED]";
|
|
763
|
+
function normalizeKeyName(name) {
|
|
764
|
+
return name.toLowerCase().replace(/[-_.]/g, "");
|
|
483
765
|
}
|
|
484
|
-
function
|
|
485
|
-
var _a;
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
766
|
+
function redactSecretsInObject(value, options) {
|
|
767
|
+
var _a, _b;
|
|
768
|
+
const normalizedSecretSet = buildSecretSet((_a = options == null ? void 0 : options.secretKeyNames) != null ? _a : DEFAULT_SECRET_KEY_NAMES);
|
|
769
|
+
const placeholder = (_b = options == null ? void 0 : options.placeholder) != null ? _b : REDACTED_PLACEHOLDER;
|
|
770
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
771
|
+
const walk = (node) => {
|
|
772
|
+
if (node === null || typeof node !== "object") return node;
|
|
773
|
+
if (seen.has(node)) return "[CIRCULAR]";
|
|
774
|
+
seen.add(node);
|
|
775
|
+
if (Array.isArray(node)) {
|
|
776
|
+
return node.map((item) => walk(item));
|
|
777
|
+
}
|
|
778
|
+
const out = {};
|
|
779
|
+
for (const [k, v] of Object.entries(node)) {
|
|
780
|
+
if (normalizedSecretSet.has(normalizeKeyName(k))) {
|
|
781
|
+
out[k] = placeholder;
|
|
782
|
+
} else {
|
|
783
|
+
out[k] = walk(v);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
return out;
|
|
787
|
+
};
|
|
788
|
+
return walk(value);
|
|
789
|
+
}
|
|
790
|
+
function buildSecretSet(names) {
|
|
791
|
+
const set = /* @__PURE__ */ new Set();
|
|
792
|
+
for (const name of names) set.add(normalizeKeyName(name));
|
|
793
|
+
return set;
|
|
794
|
+
}
|
|
795
|
+
var DEFAULT_REDACT_ATTRIBUTE_KEYS = [
|
|
796
|
+
"ai.request.providerOptions",
|
|
797
|
+
"ai.response.providerMetadata"
|
|
798
|
+
];
|
|
799
|
+
function defaultTransformSpan(span) {
|
|
800
|
+
const attrs = span.attributes;
|
|
801
|
+
if (!attrs || attrs.length === 0) return span;
|
|
802
|
+
let nextAttrs;
|
|
803
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
804
|
+
const attr = attrs[i];
|
|
805
|
+
const redacted = redactJsonAttributeValue(attr.key, attr.value);
|
|
806
|
+
if (redacted === void 0) continue;
|
|
807
|
+
if (!nextAttrs) nextAttrs = attrs.slice();
|
|
808
|
+
nextAttrs[i] = { key: attr.key, value: redacted };
|
|
809
|
+
}
|
|
810
|
+
if (!nextAttrs) return span;
|
|
811
|
+
return { ...span, attributes: nextAttrs };
|
|
812
|
+
}
|
|
813
|
+
var REDACT_JSON_ATTRIBUTE_KEYS = new Set(DEFAULT_REDACT_ATTRIBUTE_KEYS);
|
|
814
|
+
function redactJsonAttributeValue(key, value) {
|
|
815
|
+
if (!REDACT_JSON_ATTRIBUTE_KEYS.has(key)) return void 0;
|
|
816
|
+
const json = value.stringValue;
|
|
817
|
+
if (typeof json !== "string" || json.length === 0) return void 0;
|
|
818
|
+
let parsed;
|
|
819
|
+
try {
|
|
820
|
+
parsed = JSON.parse(json);
|
|
821
|
+
} catch (e) {
|
|
822
|
+
return void 0;
|
|
823
|
+
}
|
|
824
|
+
const scrubbed = redactSecretsInObject(parsed);
|
|
825
|
+
let scrubbedJson;
|
|
826
|
+
try {
|
|
827
|
+
scrubbedJson = JSON.stringify(scrubbed);
|
|
828
|
+
} catch (e) {
|
|
829
|
+
return void 0;
|
|
830
|
+
}
|
|
831
|
+
if (scrubbedJson === json) return void 0;
|
|
832
|
+
return { stringValue: scrubbedJson };
|
|
833
|
+
}
|
|
834
|
+
function applyOtelSpanAttributeLimit(limit) {
|
|
835
|
+
var _a, _b;
|
|
836
|
+
try {
|
|
837
|
+
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;
|
|
838
|
+
if (!raw) return limit;
|
|
839
|
+
const parsed = Number.parseInt(raw, 10);
|
|
840
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
841
|
+
return Math.min(limit, parsed);
|
|
842
|
+
}
|
|
843
|
+
} catch (e) {
|
|
844
|
+
}
|
|
845
|
+
return limit;
|
|
494
846
|
}
|
|
495
847
|
var TraceShipper = class {
|
|
496
848
|
constructor(opts) {
|
|
497
849
|
this.queue = [];
|
|
498
850
|
this.inFlight = /* @__PURE__ */ new Set();
|
|
499
|
-
|
|
851
|
+
this.hasShutdown = false;
|
|
852
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
500
853
|
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
501
854
|
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
502
855
|
this.enabled = opts.enabled !== false;
|
|
@@ -509,13 +862,83 @@ var TraceShipper = class {
|
|
|
509
862
|
this.prefix = `[raindrop-ai/${this.sdkName}]`;
|
|
510
863
|
this.serviceName = (_g = opts.serviceName) != null ? _g : "raindrop.core";
|
|
511
864
|
this.serviceVersion = (_h = opts.serviceVersion) != null ? _h : "0.0.0";
|
|
512
|
-
|
|
513
|
-
if (
|
|
514
|
-
this.
|
|
515
|
-
|
|
516
|
-
|
|
865
|
+
this.localDebuggerUrl = (_i = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _i : void 0;
|
|
866
|
+
if (this.debug && this.localDebuggerUrl) {
|
|
867
|
+
console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
|
|
868
|
+
}
|
|
869
|
+
this.projectId = normalizeProjectId(opts.projectId, {
|
|
870
|
+
debug: this.debug,
|
|
871
|
+
prefix: this.prefix
|
|
872
|
+
});
|
|
873
|
+
this.transformSpanHook = opts.transformSpan;
|
|
874
|
+
this.disableDefaultRedaction = opts.disableDefaultRedaction === true;
|
|
875
|
+
this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* Cap every string attribute value on the span. O(#attributes) length
|
|
879
|
+
* checks; only oversized values pay a slice. Runs AFTER the redaction
|
|
880
|
+
* pipeline so the default secret-scrub still sees parseable JSON in
|
|
881
|
+
* `ai.request.providerOptions` / `ai.response.providerMetadata` (capping
|
|
882
|
+
* first could cut a JSON blob mid-way, fail the parse, and ship secrets
|
|
883
|
+
* in the surviving prefix).
|
|
884
|
+
*
|
|
885
|
+
* A stricter `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var is honored
|
|
886
|
+
* for span content, matching the Python SDK and the OTel SDK convention.
|
|
887
|
+
*/
|
|
888
|
+
capSpanAttributes(span) {
|
|
889
|
+
var _a;
|
|
890
|
+
const maxChars = applyOtelSpanAttributeLimit(
|
|
891
|
+
resolveMaxTextFieldChars(this.maxTextFieldCharsOpt)
|
|
892
|
+
);
|
|
893
|
+
const attrs = span.attributes;
|
|
894
|
+
if (!attrs || attrs.length === 0) return span;
|
|
895
|
+
let nextAttrs;
|
|
896
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
897
|
+
const attr = attrs[i];
|
|
898
|
+
const value = (_a = attr.value) == null ? void 0 : _a.stringValue;
|
|
899
|
+
if (typeof value !== "string" || value.length <= maxChars) continue;
|
|
900
|
+
if (!nextAttrs) nextAttrs = attrs.slice();
|
|
901
|
+
nextAttrs[i] = {
|
|
902
|
+
key: attr.key,
|
|
903
|
+
value: { ...attr.value, stringValue: capText(value, maxChars) }
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
if (!nextAttrs) return span;
|
|
907
|
+
return { ...span, attributes: nextAttrs };
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Apply the user `transformSpan` hook (if any) followed by the default
|
|
911
|
+
* redactor (unless disabled). Returns either the (possibly new) span to
|
|
912
|
+
* ship, or `null` to drop the span entirely.
|
|
913
|
+
*
|
|
914
|
+
* Ordering: user hook runs first so callers can rewrite the span freely
|
|
915
|
+
* (rename attrs, add new ones, scrub things the default doesn't know
|
|
916
|
+
* about). The default redactor then runs on whatever the user produced,
|
|
917
|
+
* acting as the always-on floor for documented BYOK secrets. If the user
|
|
918
|
+
* sets `disableDefaultRedaction: true`, the floor is skipped.
|
|
919
|
+
*
|
|
920
|
+
* Fail-closed: if the user hook throws, the span is dropped — a buggy
|
|
921
|
+
* hook can never accidentally ship raw, un-redacted spans.
|
|
922
|
+
*/
|
|
923
|
+
redactSpan(span) {
|
|
924
|
+
let current = span;
|
|
925
|
+
if (this.transformSpanHook) {
|
|
926
|
+
try {
|
|
927
|
+
const result = this.transformSpanHook(current);
|
|
928
|
+
if (result === null) return null;
|
|
929
|
+
if (result !== void 0) current = result;
|
|
930
|
+
} catch (err) {
|
|
931
|
+
if (this.debug) {
|
|
932
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
933
|
+
console.warn(`${this.prefix} transformSpan hook threw: ${msg}`);
|
|
934
|
+
}
|
|
935
|
+
return null;
|
|
517
936
|
}
|
|
518
937
|
}
|
|
938
|
+
if (!this.disableDefaultRedaction) {
|
|
939
|
+
current = defaultTransformSpan(current);
|
|
940
|
+
}
|
|
941
|
+
return this.capSpanAttributes(current);
|
|
519
942
|
}
|
|
520
943
|
isDebugEnabled() {
|
|
521
944
|
return this.debug;
|
|
@@ -523,6 +946,9 @@ var TraceShipper = class {
|
|
|
523
946
|
authHeaders() {
|
|
524
947
|
return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
|
|
525
948
|
}
|
|
949
|
+
requestHeaders() {
|
|
950
|
+
return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
|
|
951
|
+
}
|
|
526
952
|
startSpan(args) {
|
|
527
953
|
var _a, _b;
|
|
528
954
|
const ids = createSpanIds(args.parent);
|
|
@@ -533,8 +959,8 @@ var TraceShipper = class {
|
|
|
533
959
|
];
|
|
534
960
|
if ((_b = args.attributes) == null ? void 0 : _b.length) attrs.push(...args.attributes);
|
|
535
961
|
const span = { ids, name: args.name, startTimeUnixNano: started, attributes: attrs };
|
|
536
|
-
|
|
537
|
-
|
|
962
|
+
this.mirrorToLocalDebugger(
|
|
963
|
+
buildOtlpSpan({
|
|
538
964
|
ids: span.ids,
|
|
539
965
|
name: span.name,
|
|
540
966
|
startTimeUnixNano: span.startTimeUnixNano,
|
|
@@ -542,16 +968,21 @@ var TraceShipper = class {
|
|
|
542
968
|
// placeholder — will be updated on endSpan
|
|
543
969
|
attributes: span.attributes,
|
|
544
970
|
status: { code: SpanStatusCode.UNSET }
|
|
545
|
-
})
|
|
546
|
-
|
|
547
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
548
|
-
baseUrl: this.localDebuggerUrl,
|
|
549
|
-
debug: false,
|
|
550
|
-
sdkName: this.sdkName
|
|
551
|
-
});
|
|
552
|
-
}
|
|
971
|
+
})
|
|
972
|
+
);
|
|
553
973
|
return span;
|
|
554
974
|
}
|
|
975
|
+
mirrorToLocalDebugger(span) {
|
|
976
|
+
if (!this.localDebuggerUrl) return;
|
|
977
|
+
const redacted = this.redactSpan(span);
|
|
978
|
+
if (redacted === null) return;
|
|
979
|
+
const body = buildExportTraceServiceRequest([redacted], this.serviceName, this.serviceVersion);
|
|
980
|
+
mirrorTraceExportToLocalDebugger(body, {
|
|
981
|
+
baseUrl: this.localDebuggerUrl,
|
|
982
|
+
debug: false,
|
|
983
|
+
sdkName: this.sdkName
|
|
984
|
+
});
|
|
985
|
+
}
|
|
555
986
|
endSpan(span, extra) {
|
|
556
987
|
var _a, _b;
|
|
557
988
|
if (span.endTimeUnixNano) return;
|
|
@@ -573,14 +1004,7 @@ var TraceShipper = class {
|
|
|
573
1004
|
status
|
|
574
1005
|
});
|
|
575
1006
|
this.enqueue(otlp);
|
|
576
|
-
|
|
577
|
-
const body = buildExportTraceServiceRequest([otlp], this.serviceName, this.serviceVersion);
|
|
578
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
579
|
-
baseUrl: this.localDebuggerUrl,
|
|
580
|
-
debug: false,
|
|
581
|
-
sdkName: this.sdkName
|
|
582
|
-
});
|
|
583
|
-
}
|
|
1007
|
+
this.mirrorToLocalDebugger(otlp);
|
|
584
1008
|
}
|
|
585
1009
|
createSpan(args) {
|
|
586
1010
|
var _a;
|
|
@@ -598,14 +1022,7 @@ var TraceShipper = class {
|
|
|
598
1022
|
status: args.status
|
|
599
1023
|
});
|
|
600
1024
|
this.enqueue(otlp);
|
|
601
|
-
|
|
602
|
-
const body = buildExportTraceServiceRequest([otlp], this.serviceName, this.serviceVersion);
|
|
603
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
604
|
-
baseUrl: this.localDebuggerUrl,
|
|
605
|
-
debug: false,
|
|
606
|
-
sdkName: this.sdkName
|
|
607
|
-
});
|
|
608
|
-
}
|
|
1025
|
+
this.mirrorToLocalDebugger(otlp);
|
|
609
1026
|
}
|
|
610
1027
|
enqueue(span) {
|
|
611
1028
|
if (!this.enabled) return;
|
|
@@ -617,10 +1034,12 @@ var TraceShipper = class {
|
|
|
617
1034
|
)}`
|
|
618
1035
|
);
|
|
619
1036
|
}
|
|
1037
|
+
const redacted = this.redactSpan(span);
|
|
1038
|
+
if (redacted === null) return;
|
|
620
1039
|
if (this.queue.length >= this.maxQueueSize) {
|
|
621
1040
|
this.queue.shift();
|
|
622
1041
|
}
|
|
623
|
-
this.queue.push(
|
|
1042
|
+
this.queue.push(redacted);
|
|
624
1043
|
if (this.queue.length >= this.maxBatchSize) {
|
|
625
1044
|
void this.flush().catch(() => {
|
|
626
1045
|
});
|
|
@@ -642,6 +1061,17 @@ var TraceShipper = class {
|
|
|
642
1061
|
}
|
|
643
1062
|
while (this.queue.length > 0) {
|
|
644
1063
|
const batch = this.queue.splice(0, this.maxBatchSize);
|
|
1064
|
+
if (!this.writeKey) continue;
|
|
1065
|
+
const opts = this.requestOpts();
|
|
1066
|
+
if (!opts) {
|
|
1067
|
+
rateLimitedLog(
|
|
1068
|
+
`${this.prefix}.shutdown_deadline`,
|
|
1069
|
+
() => console.warn(
|
|
1070
|
+
`${this.prefix} shutdown flush deadline exceeded; dropping ${batch.length} spans`
|
|
1071
|
+
)
|
|
1072
|
+
);
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
645
1075
|
const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
|
|
646
1076
|
const url = `${this.baseUrl}traces`;
|
|
647
1077
|
if (this.debug) {
|
|
@@ -650,11 +1080,7 @@ var TraceShipper = class {
|
|
|
650
1080
|
endpoint: url
|
|
651
1081
|
});
|
|
652
1082
|
}
|
|
653
|
-
const p = postJson(url, body, this.
|
|
654
|
-
maxAttempts: 3,
|
|
655
|
-
debug: this.debug,
|
|
656
|
-
sdkName: this.sdkName
|
|
657
|
-
});
|
|
1083
|
+
const p = postJson(url, body, this.requestHeaders(), opts);
|
|
658
1084
|
this.inFlight.add(p);
|
|
659
1085
|
try {
|
|
660
1086
|
try {
|
|
@@ -662,21 +1088,61 @@ var TraceShipper = class {
|
|
|
662
1088
|
if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
|
|
663
1089
|
} catch (err) {
|
|
664
1090
|
const msg = err instanceof Error ? err.message : String(err);
|
|
665
|
-
|
|
1091
|
+
rateLimitedLog(
|
|
1092
|
+
`${this.prefix}.send_spans_failed`,
|
|
1093
|
+
() => console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`)
|
|
1094
|
+
);
|
|
666
1095
|
}
|
|
667
1096
|
} finally {
|
|
668
1097
|
this.inFlight.delete(p);
|
|
669
1098
|
}
|
|
670
1099
|
}
|
|
671
1100
|
}
|
|
1101
|
+
/** See EventShipper.requestOpts — same shutdown-budget semantics. */
|
|
1102
|
+
requestOpts() {
|
|
1103
|
+
if (this.shutdownDeadlineAt !== void 0) {
|
|
1104
|
+
const remainingMs = this.shutdownDeadlineAt - Date.now();
|
|
1105
|
+
if (remainingMs <= 0) return null;
|
|
1106
|
+
return {
|
|
1107
|
+
maxAttempts: 1,
|
|
1108
|
+
debug: this.debug,
|
|
1109
|
+
sdkName: this.sdkName,
|
|
1110
|
+
timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
if (this.hasShutdown) {
|
|
1114
|
+
return {
|
|
1115
|
+
maxAttempts: 1,
|
|
1116
|
+
debug: this.debug,
|
|
1117
|
+
sdkName: this.sdkName,
|
|
1118
|
+
timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
|
|
1122
|
+
}
|
|
672
1123
|
async shutdown() {
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
1124
|
+
this.hasShutdown = true;
|
|
1125
|
+
this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
|
|
1126
|
+
try {
|
|
1127
|
+
if (this.timer) {
|
|
1128
|
+
clearTimeout(this.timer);
|
|
1129
|
+
this.timer = void 0;
|
|
1130
|
+
}
|
|
1131
|
+
const drain = async () => {
|
|
1132
|
+
await this.flush();
|
|
1133
|
+
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
1134
|
+
})));
|
|
1135
|
+
};
|
|
1136
|
+
const settled = await raceWithTimeout(drain(), SHUTDOWN_DEADLINE_MS);
|
|
1137
|
+
if (!settled) {
|
|
1138
|
+
rateLimitedLog(
|
|
1139
|
+
`${this.prefix}.shutdown_deadline`,
|
|
1140
|
+
() => console.warn(`${this.prefix} shutdown flush deadline exceeded; abandoning in-flight spans`)
|
|
1141
|
+
);
|
|
1142
|
+
}
|
|
1143
|
+
} finally {
|
|
1144
|
+
this.shutdownDeadlineAt = void 0;
|
|
676
1145
|
}
|
|
677
|
-
await this.flush();
|
|
678
|
-
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
679
|
-
})));
|
|
680
1146
|
}
|
|
681
1147
|
};
|
|
682
1148
|
|
|
@@ -684,6 +1150,133 @@ var TraceShipper = class {
|
|
|
684
1150
|
var import_async_hooks = require("async_hooks");
|
|
685
1151
|
globalThis.RAINDROP_ASYNC_LOCAL_STORAGE = import_async_hooks.AsyncLocalStorage;
|
|
686
1152
|
|
|
1153
|
+
// src/truncation.ts
|
|
1154
|
+
var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
|
|
1155
|
+
var DEFAULT_MAX_TEXT_FIELD_CHARS2 = 1e6;
|
|
1156
|
+
var MAX_DEPTH = 12;
|
|
1157
|
+
var configuredMaxTextFieldChars;
|
|
1158
|
+
function setMaxTextFieldChars(limit) {
|
|
1159
|
+
if (limit === void 0) return;
|
|
1160
|
+
if (typeof limit === "number" && Number.isFinite(limit) && limit > 0) {
|
|
1161
|
+
configuredMaxTextFieldChars = Math.floor(limit);
|
|
1162
|
+
} else {
|
|
1163
|
+
console.warn(`[raindrop-ai] maxTextFieldChars=${String(limit)} ignored; must be > 0`);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
function otelEnvLimit() {
|
|
1167
|
+
var _a;
|
|
1168
|
+
if (typeof process === "undefined") return void 0;
|
|
1169
|
+
const raw = (_a = process.env) == null ? void 0 : _a.OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT;
|
|
1170
|
+
if (!raw) return void 0;
|
|
1171
|
+
const parsed = Number.parseInt(raw, 10);
|
|
1172
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
|
|
1173
|
+
}
|
|
1174
|
+
function effectiveMaxTextFieldChars() {
|
|
1175
|
+
const base = configuredMaxTextFieldChars != null ? configuredMaxTextFieldChars : DEFAULT_MAX_TEXT_FIELD_CHARS2;
|
|
1176
|
+
const env = otelEnvLimit();
|
|
1177
|
+
return env !== void 0 && env < base ? env : base;
|
|
1178
|
+
}
|
|
1179
|
+
function truncateToLimit2(text, limit) {
|
|
1180
|
+
if (limit > TRUNCATION_MARKER2.length) {
|
|
1181
|
+
return text.slice(0, limit - TRUNCATION_MARKER2.length) + TRUNCATION_MARKER2;
|
|
1182
|
+
}
|
|
1183
|
+
return text.slice(0, limit);
|
|
1184
|
+
}
|
|
1185
|
+
function capText2(value, limit) {
|
|
1186
|
+
if (typeof value !== "string") return value;
|
|
1187
|
+
const max = limit != null ? limit : effectiveMaxTextFieldChars();
|
|
1188
|
+
if (value.length <= max) return value;
|
|
1189
|
+
return truncateToLimit2(value, max);
|
|
1190
|
+
}
|
|
1191
|
+
function boundedClone2(value, budget, depth) {
|
|
1192
|
+
var _a, _b;
|
|
1193
|
+
if (budget.remaining <= 0) return TRUNCATION_MARKER2;
|
|
1194
|
+
if (typeof value === "string") {
|
|
1195
|
+
if (value.length > budget.remaining) {
|
|
1196
|
+
const taken = value.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
|
|
1197
|
+
budget.remaining = 0;
|
|
1198
|
+
return taken;
|
|
1199
|
+
}
|
|
1200
|
+
budget.remaining -= Math.max(value.length, 1);
|
|
1201
|
+
return value;
|
|
1202
|
+
}
|
|
1203
|
+
if (value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
1204
|
+
budget.remaining -= 8;
|
|
1205
|
+
return value;
|
|
1206
|
+
}
|
|
1207
|
+
if (typeof value !== "object") {
|
|
1208
|
+
budget.remaining -= 1;
|
|
1209
|
+
return value;
|
|
1210
|
+
}
|
|
1211
|
+
if (value instanceof Uint8Array) {
|
|
1212
|
+
const maxBytes = Math.max(0, Math.ceil(budget.remaining * 3 / 4));
|
|
1213
|
+
if (value.byteLength > maxBytes) {
|
|
1214
|
+
const taken = base64Encode(value.subarray(0, maxBytes)) + TRUNCATION_MARKER2;
|
|
1215
|
+
budget.remaining = 0;
|
|
1216
|
+
return taken;
|
|
1217
|
+
}
|
|
1218
|
+
const encoded = base64Encode(value);
|
|
1219
|
+
budget.remaining -= Math.max(encoded.length, 1);
|
|
1220
|
+
return encoded;
|
|
1221
|
+
}
|
|
1222
|
+
if (depth >= MAX_DEPTH) {
|
|
1223
|
+
budget.remaining -= 16;
|
|
1224
|
+
const name = (_b = (_a = value.constructor) == null ? void 0 : _a.name) != null ? _b : "object";
|
|
1225
|
+
return `<max depth: ${name}>`;
|
|
1226
|
+
}
|
|
1227
|
+
const toJSON = value.toJSON;
|
|
1228
|
+
if (typeof toJSON === "function") {
|
|
1229
|
+
budget.remaining -= 2;
|
|
1230
|
+
return boundedClone2(toJSON.call(value), budget, depth + 1);
|
|
1231
|
+
}
|
|
1232
|
+
if (Array.isArray(value)) {
|
|
1233
|
+
budget.remaining -= 2;
|
|
1234
|
+
const out2 = [];
|
|
1235
|
+
for (const item of value) {
|
|
1236
|
+
if (budget.remaining <= 0) {
|
|
1237
|
+
out2.push(TRUNCATION_MARKER2);
|
|
1238
|
+
break;
|
|
1239
|
+
}
|
|
1240
|
+
out2.push(boundedClone2(item, budget, depth + 1));
|
|
1241
|
+
}
|
|
1242
|
+
return out2;
|
|
1243
|
+
}
|
|
1244
|
+
budget.remaining -= 2;
|
|
1245
|
+
const out = {};
|
|
1246
|
+
for (const key in value) {
|
|
1247
|
+
if (!Object.prototype.hasOwnProperty.call(value, key)) continue;
|
|
1248
|
+
if (budget.remaining <= 0) {
|
|
1249
|
+
out["..."] = TRUNCATION_MARKER2;
|
|
1250
|
+
break;
|
|
1251
|
+
}
|
|
1252
|
+
let cappedKey = key;
|
|
1253
|
+
if (key.length > budget.remaining) {
|
|
1254
|
+
cappedKey = key.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
|
|
1255
|
+
budget.remaining = 0;
|
|
1256
|
+
out[cappedKey] = TRUNCATION_MARKER2;
|
|
1257
|
+
break;
|
|
1258
|
+
}
|
|
1259
|
+
budget.remaining -= Math.max(key.length, 1);
|
|
1260
|
+
out[cappedKey] = boundedClone2(value[key], budget, depth + 1);
|
|
1261
|
+
}
|
|
1262
|
+
return out;
|
|
1263
|
+
}
|
|
1264
|
+
function boundedStringify(value, limit) {
|
|
1265
|
+
const max = limit != null ? limit : effectiveMaxTextFieldChars();
|
|
1266
|
+
try {
|
|
1267
|
+
if (typeof value === "string") {
|
|
1268
|
+
const text2 = JSON.stringify(capText2(value, max));
|
|
1269
|
+
return text2.length <= max ? text2 : truncateToLimit2(text2, max);
|
|
1270
|
+
}
|
|
1271
|
+
const pruned = boundedClone2(value, { remaining: max + TRUNCATION_MARKER2.length + 256 }, 0);
|
|
1272
|
+
const text = JSON.stringify(pruned);
|
|
1273
|
+
if (text === void 0) return void 0;
|
|
1274
|
+
return text.length <= max ? text : truncateToLimit2(text, max);
|
|
1275
|
+
} catch (e) {
|
|
1276
|
+
return void 0;
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
|
|
687
1280
|
// src/callback-handler.ts
|
|
688
1281
|
var LANGGRAPH_INTERNAL_NAMES = /* @__PURE__ */ new Set([
|
|
689
1282
|
"LangGraph",
|
|
@@ -773,7 +1366,7 @@ var RaindropCallbackHandler = class extends import_base.BaseCallbackHandler {
|
|
|
773
1366
|
await this.eventShipper.patch(eventId, {
|
|
774
1367
|
userId: this.userId,
|
|
775
1368
|
convoId: this.convoId,
|
|
776
|
-
input: prompts.join("\n"),
|
|
1369
|
+
input: capText2(prompts.join("\n")),
|
|
777
1370
|
properties: buildProperties(tags, metadata),
|
|
778
1371
|
isPending: true
|
|
779
1372
|
});
|
|
@@ -803,7 +1396,7 @@ var RaindropCallbackHandler = class extends import_base.BaseCallbackHandler {
|
|
|
803
1396
|
return role === "human";
|
|
804
1397
|
});
|
|
805
1398
|
const lastMsg = userMessages.length > 0 ? userMessages[userMessages.length - 1] : allMessages[allMessages.length - 1];
|
|
806
|
-
const inputText = lastMsg ? typeof lastMsg.content === "string" ? lastMsg.content : safeStringify(lastMsg.content) : void 0;
|
|
1399
|
+
const inputText = lastMsg ? typeof lastMsg.content === "string" ? capText2(lastMsg.content) : safeStringify(lastMsg.content) : void 0;
|
|
807
1400
|
await this.eventShipper.patch(eventId, {
|
|
808
1401
|
userId: this.userId,
|
|
809
1402
|
convoId: this.convoId,
|
|
@@ -833,7 +1426,7 @@ var RaindropCallbackHandler = class extends import_base.BaseCallbackHandler {
|
|
|
833
1426
|
if (promptTokens != null) properties["ai.usage.prompt_tokens"] = promptTokens;
|
|
834
1427
|
if (completionTokens != null) properties["ai.usage.completion_tokens"] = completionTokens;
|
|
835
1428
|
await this.finalizeEventIfRoot(runId, {
|
|
836
|
-
output: outputText,
|
|
1429
|
+
output: capText2(outputText),
|
|
837
1430
|
model,
|
|
838
1431
|
properties: Object.keys(properties).length > 0 ? properties : void 0
|
|
839
1432
|
});
|
|
@@ -926,7 +1519,7 @@ var RaindropCallbackHandler = class extends import_base.BaseCallbackHandler {
|
|
|
926
1519
|
try {
|
|
927
1520
|
const span = this.spans.get(runId);
|
|
928
1521
|
if (span) {
|
|
929
|
-
const outputStr = typeof output === "string" ? output : safeStringify(output);
|
|
1522
|
+
const outputStr = typeof output === "string" ? output : safeStringify(output, 4096);
|
|
930
1523
|
this.traceShipper.endSpan(span, {
|
|
931
1524
|
attributes: [attrString("tool.output", truncate(outputStr, 4096))]
|
|
932
1525
|
});
|
|
@@ -1011,7 +1604,7 @@ var RaindropCallbackHandler = class extends import_base.BaseCallbackHandler {
|
|
|
1011
1604
|
attributes: [
|
|
1012
1605
|
attrString("agent.tool", action.tool),
|
|
1013
1606
|
attrString("agent.tool_input", truncate(
|
|
1014
|
-
typeof action.toolInput === "string" ? action.toolInput : safeStringify(action.toolInput),
|
|
1607
|
+
typeof action.toolInput === "string" ? action.toolInput : safeStringify(action.toolInput, 4096),
|
|
1015
1608
|
4096
|
|
1016
1609
|
))
|
|
1017
1610
|
]
|
|
@@ -1053,13 +1646,10 @@ function buildProperties(tags, metadata) {
|
|
|
1053
1646
|
if (metadata) Object.assign(props, metadata);
|
|
1054
1647
|
return props;
|
|
1055
1648
|
}
|
|
1056
|
-
function safeStringify(value) {
|
|
1649
|
+
function safeStringify(value, limit) {
|
|
1057
1650
|
var _a;
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
} catch (e) {
|
|
1061
|
-
return String(value);
|
|
1062
|
-
}
|
|
1651
|
+
if (value === void 0) return "";
|
|
1652
|
+
return (_a = boundedStringify(value, limit)) != null ? _a : String(value);
|
|
1063
1653
|
}
|
|
1064
1654
|
function truncate(str, maxLen) {
|
|
1065
1655
|
if (str.length <= maxLen) return str;
|
|
@@ -1078,14 +1668,14 @@ function extractLLMMetadata(output) {
|
|
|
1078
1668
|
const usage = (_g = usageMeta != null ? usageMeta : tokenUsage) != null ? _g : estimatedTokens;
|
|
1079
1669
|
const promptTokens = (_i = (_h = usage == null ? void 0 : usage.input_tokens) != null ? _h : usage == null ? void 0 : usage.prompt_tokens) != null ? _i : usage == null ? void 0 : usage.promptTokens;
|
|
1080
1670
|
const completionTokens = (_k = (_j = usage == null ? void 0 : usage.output_tokens) != null ? _j : usage == null ? void 0 : usage.completion_tokens) != null ? _k : usage == null ? void 0 : usage.completionTokens;
|
|
1081
|
-
const outputText = (_m = (_l = gen == null ? void 0 : gen.text) != null ? _l : typeof (message == null ? void 0 : message.content) === "string" ? message.content : void 0) != null ? _m : (message == null ? void 0 : message.content) ?
|
|
1671
|
+
const outputText = (_m = (_l = gen == null ? void 0 : gen.text) != null ? _l : typeof (message == null ? void 0 : message.content) === "string" ? message.content : void 0) != null ? _m : (message == null ? void 0 : message.content) ? boundedStringify(message.content) : void 0;
|
|
1082
1672
|
return { model, promptTokens, completionTokens, outputText };
|
|
1083
1673
|
}
|
|
1084
1674
|
|
|
1085
1675
|
// package.json
|
|
1086
1676
|
var package_default = {
|
|
1087
1677
|
name: "@raindrop-ai/langchain",
|
|
1088
|
-
version: "0.0.
|
|
1678
|
+
version: "0.0.5",
|
|
1089
1679
|
description: "Raindrop integration for LangChain",
|
|
1090
1680
|
main: "dist/index.js",
|
|
1091
1681
|
module: "dist/index.mjs",
|
|
@@ -1159,6 +1749,7 @@ function createRaindropLangChain(opts) {
|
|
|
1159
1749
|
var _a, _b;
|
|
1160
1750
|
const writeKey = opts.writeKey;
|
|
1161
1751
|
const enabled = !!writeKey;
|
|
1752
|
+
setMaxTextFieldChars(opts.maxTextFieldChars);
|
|
1162
1753
|
if (!writeKey) {
|
|
1163
1754
|
console.warn("[raindrop-ai/langchain] writeKey not provided; telemetry shipping is disabled");
|
|
1164
1755
|
}
|
|
@@ -1167,6 +1758,7 @@ function createRaindropLangChain(opts) {
|
|
|
1167
1758
|
endpoint: opts.endpoint,
|
|
1168
1759
|
enabled,
|
|
1169
1760
|
debug: (_a = opts.debug) != null ? _a : false,
|
|
1761
|
+
projectId: opts.projectId,
|
|
1170
1762
|
sdkName: "langchain",
|
|
1171
1763
|
libraryName,
|
|
1172
1764
|
libraryVersion
|
|
@@ -1176,6 +1768,7 @@ function createRaindropLangChain(opts) {
|
|
|
1176
1768
|
endpoint: opts.endpoint,
|
|
1177
1769
|
enabled,
|
|
1178
1770
|
debug: (_b = opts.debug) != null ? _b : false,
|
|
1771
|
+
projectId: opts.projectId,
|
|
1179
1772
|
sdkName: "langchain",
|
|
1180
1773
|
serviceName: "raindrop.langchain",
|
|
1181
1774
|
serviceVersion: libraryVersion
|