@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.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/callback-handler.ts
|
|
2
2
|
import { BaseCallbackHandler } from "@langchain/core/callbacks/base";
|
|
3
3
|
|
|
4
|
-
// ../core/dist/chunk-
|
|
4
|
+
// ../core/dist/chunk-SK6EJEO7.js
|
|
5
5
|
function getCrypto() {
|
|
6
6
|
const c = globalThis.crypto;
|
|
7
7
|
return c;
|
|
@@ -56,6 +56,8 @@ function base64Encode(bytes) {
|
|
|
56
56
|
}
|
|
57
57
|
return out;
|
|
58
58
|
}
|
|
59
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
60
|
+
var MAX_RETRY_DELAY_MS = 3e4;
|
|
59
61
|
function wait(ms) {
|
|
60
62
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
61
63
|
}
|
|
@@ -63,6 +65,46 @@ function formatEndpoint(endpoint) {
|
|
|
63
65
|
if (!endpoint) return void 0;
|
|
64
66
|
return endpoint.endsWith("/") ? endpoint : `${endpoint}/`;
|
|
65
67
|
}
|
|
68
|
+
function redactUrlForLog(url) {
|
|
69
|
+
try {
|
|
70
|
+
const parsed = new URL(url);
|
|
71
|
+
parsed.username = "";
|
|
72
|
+
parsed.password = "";
|
|
73
|
+
parsed.search = "";
|
|
74
|
+
parsed.hash = "";
|
|
75
|
+
return parsed.toString();
|
|
76
|
+
} catch (e) {
|
|
77
|
+
return "<unparseable-url>";
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
var RATE_LIMITED_LOG_INTERVAL_MS = 3e4;
|
|
81
|
+
var rateLimitedLogLast = /* @__PURE__ */ new Map();
|
|
82
|
+
function rateLimitedLog(key, log) {
|
|
83
|
+
const now = Date.now();
|
|
84
|
+
const last = rateLimitedLogLast.get(key);
|
|
85
|
+
if (last !== void 0 && now - last < RATE_LIMITED_LOG_INTERVAL_MS) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
rateLimitedLogLast.set(key, now);
|
|
89
|
+
log();
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
async function raceWithTimeout(promise, timeoutMs) {
|
|
93
|
+
let timer;
|
|
94
|
+
const settledInTime = await Promise.race([
|
|
95
|
+
promise.then(
|
|
96
|
+
() => true,
|
|
97
|
+
() => true
|
|
98
|
+
),
|
|
99
|
+
new Promise((resolve) => {
|
|
100
|
+
var _a;
|
|
101
|
+
timer = setTimeout(() => resolve(false), Math.max(0, timeoutMs));
|
|
102
|
+
(_a = timer.unref) == null ? void 0 : _a.call(timer);
|
|
103
|
+
})
|
|
104
|
+
]);
|
|
105
|
+
if (timer) clearTimeout(timer);
|
|
106
|
+
return settledInTime;
|
|
107
|
+
}
|
|
66
108
|
function parseRetryAfter(headers) {
|
|
67
109
|
var _a;
|
|
68
110
|
const value = (_a = headers.get("Retry-After")) != null ? _a : headers.get("retry-after");
|
|
@@ -79,12 +121,12 @@ function parseRetryAfter(headers) {
|
|
|
79
121
|
function getRetryDelayMs(attemptNumber, previousError) {
|
|
80
122
|
if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
|
|
81
123
|
const v = previousError.retryAfterMs;
|
|
82
|
-
if (typeof v === "number") return Math.max(0, v);
|
|
124
|
+
if (typeof v === "number") return Math.min(Math.max(0, v), MAX_RETRY_DELAY_MS);
|
|
83
125
|
}
|
|
84
126
|
if (attemptNumber <= 1) return 0;
|
|
85
127
|
const base = 500;
|
|
86
128
|
const factor = Math.pow(2, attemptNumber - 2);
|
|
87
|
-
return base * factor;
|
|
129
|
+
return Math.min(base * factor, MAX_RETRY_DELAY_MS);
|
|
88
130
|
}
|
|
89
131
|
async function withRetry(operation, opName, opts) {
|
|
90
132
|
const prefix = opts.sdkName ? `[raindrop-ai/${opts.sdkName}]` : "[raindrop-ai/core]";
|
|
@@ -119,7 +161,9 @@ async function withRetry(operation, opName, opts) {
|
|
|
119
161
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
120
162
|
}
|
|
121
163
|
async function postJson(url, body, headers, opts) {
|
|
122
|
-
|
|
164
|
+
var _a;
|
|
165
|
+
const opName = `POST ${redactUrlForLog(url)}`;
|
|
166
|
+
const timeoutMs = (_a = opts.timeoutMs) != null ? _a : DEFAULT_REQUEST_TIMEOUT_MS;
|
|
123
167
|
await withRetry(
|
|
124
168
|
async () => {
|
|
125
169
|
const resp = await fetch(url, {
|
|
@@ -128,7 +172,8 @@ async function postJson(url, body, headers, opts) {
|
|
|
128
172
|
"Content-Type": "application/json",
|
|
129
173
|
...headers
|
|
130
174
|
},
|
|
131
|
-
body: JSON.stringify(body)
|
|
175
|
+
body: JSON.stringify(body),
|
|
176
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
132
177
|
});
|
|
133
178
|
if (!resp.ok) {
|
|
134
179
|
const text = await resp.text().catch(() => "");
|
|
@@ -145,6 +190,27 @@ async function postJson(url, body, headers, opts) {
|
|
|
145
190
|
opts
|
|
146
191
|
);
|
|
147
192
|
}
|
|
193
|
+
var DEFAULT_MAX_TEXT_FIELD_CHARS = 1e6;
|
|
194
|
+
var TRUNCATION_MARKER = "...[truncated by raindrop]";
|
|
195
|
+
var currentDefaultMaxTextFieldChars = DEFAULT_MAX_TEXT_FIELD_CHARS;
|
|
196
|
+
function resolveMaxTextFieldChars(value) {
|
|
197
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
198
|
+
return Math.floor(value);
|
|
199
|
+
}
|
|
200
|
+
return currentDefaultMaxTextFieldChars;
|
|
201
|
+
}
|
|
202
|
+
function truncateToLimit(text, limit) {
|
|
203
|
+
if (limit > TRUNCATION_MARKER.length) {
|
|
204
|
+
return text.slice(0, limit - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;
|
|
205
|
+
}
|
|
206
|
+
return text.slice(0, Math.max(0, limit));
|
|
207
|
+
}
|
|
208
|
+
function capText(value, limit) {
|
|
209
|
+
if (typeof value !== "string") return value;
|
|
210
|
+
const max = limit != null ? limit : currentDefaultMaxTextFieldChars;
|
|
211
|
+
if (value.length <= max) return value;
|
|
212
|
+
return truncateToLimit(value, max);
|
|
213
|
+
}
|
|
148
214
|
var SpanStatusCode = {
|
|
149
215
|
UNSET: 0,
|
|
150
216
|
OK: 1,
|
|
@@ -202,6 +268,112 @@ function buildExportTraceServiceRequest(spans, serviceName = "raindrop.core", se
|
|
|
202
268
|
]
|
|
203
269
|
};
|
|
204
270
|
}
|
|
271
|
+
var LOCAL_DEBUGGER_ENV_VAR = "RAINDROP_LOCAL_DEBUGGER";
|
|
272
|
+
var WORKSHOP_ENV_VAR = "RAINDROP_WORKSHOP";
|
|
273
|
+
var DEFAULT_LOCAL_WORKSHOP_URL = "http://localhost:5899/v1/";
|
|
274
|
+
function readEnvVar(name) {
|
|
275
|
+
var _a;
|
|
276
|
+
try {
|
|
277
|
+
const env = (_a = globalThis == null ? void 0 : globalThis.process) == null ? void 0 : _a.env;
|
|
278
|
+
if (env && typeof env[name] === "string" && env[name].length > 0) {
|
|
279
|
+
return env[name];
|
|
280
|
+
}
|
|
281
|
+
} catch (e) {
|
|
282
|
+
}
|
|
283
|
+
return void 0;
|
|
284
|
+
}
|
|
285
|
+
function readWorkshopEnv() {
|
|
286
|
+
const raw = readEnvVar(WORKSHOP_ENV_VAR);
|
|
287
|
+
if (raw === void 0) return void 0;
|
|
288
|
+
const trimmed = raw.trim();
|
|
289
|
+
if (trimmed.length === 0) return void 0;
|
|
290
|
+
if (/^https?:\/\//i.test(trimmed)) return { url: trimmed };
|
|
291
|
+
if (/^(1|true|yes|on)$/i.test(trimmed)) return "enable";
|
|
292
|
+
if (/^(0|false|no|off)$/i.test(trimmed)) return "disable";
|
|
293
|
+
return void 0;
|
|
294
|
+
}
|
|
295
|
+
function isLocalDevHost(hostname) {
|
|
296
|
+
if (!hostname) return false;
|
|
297
|
+
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "0.0.0.0" || hostname === "::1") {
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
if (hostname.endsWith(".localhost")) return true;
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
function readRuntimeHostname() {
|
|
304
|
+
try {
|
|
305
|
+
const loc = globalThis == null ? void 0 : globalThis.location;
|
|
306
|
+
if (loc && typeof loc.hostname === "string" && loc.hostname.length > 0) {
|
|
307
|
+
return loc.hostname;
|
|
308
|
+
}
|
|
309
|
+
} catch (e) {
|
|
310
|
+
}
|
|
311
|
+
return void 0;
|
|
312
|
+
}
|
|
313
|
+
function shouldAutoEnableLocalWorkshop() {
|
|
314
|
+
if (isLocalDevHost(readRuntimeHostname())) return true;
|
|
315
|
+
if (readEnvVar("NODE_ENV") === "development") return true;
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
function resolveLocalDebuggerBaseUrl(baseUrl) {
|
|
319
|
+
var _a, _b, _c;
|
|
320
|
+
if (baseUrl === null) return null;
|
|
321
|
+
if (typeof baseUrl === "string" && baseUrl.length > 0) {
|
|
322
|
+
return (_a = formatEndpoint(baseUrl)) != null ? _a : null;
|
|
323
|
+
}
|
|
324
|
+
const explicitUrlEnv = readEnvVar(LOCAL_DEBUGGER_ENV_VAR);
|
|
325
|
+
if (explicitUrlEnv) return (_b = formatEndpoint(explicitUrlEnv)) != null ? _b : null;
|
|
326
|
+
const workshopEnv = readWorkshopEnv();
|
|
327
|
+
if (workshopEnv === "disable") return null;
|
|
328
|
+
if (workshopEnv === "enable") return DEFAULT_LOCAL_WORKSHOP_URL;
|
|
329
|
+
if (workshopEnv && "url" in workshopEnv) return (_c = formatEndpoint(workshopEnv.url)) != null ? _c : null;
|
|
330
|
+
if (shouldAutoEnableLocalWorkshop()) return DEFAULT_LOCAL_WORKSHOP_URL;
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
function mirrorTraceExportToLocalDebugger(body, options = {}) {
|
|
334
|
+
var _a;
|
|
335
|
+
const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
|
|
336
|
+
if (!baseUrl) return;
|
|
337
|
+
void postJson(`${baseUrl}traces`, body, {}, {
|
|
338
|
+
maxAttempts: 1,
|
|
339
|
+
debug: (_a = options.debug) != null ? _a : false,
|
|
340
|
+
sdkName: options.sdkName
|
|
341
|
+
}).catch(() => {
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
function mirrorPartialEventToLocalDebugger(event, options = {}) {
|
|
345
|
+
var _a;
|
|
346
|
+
const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
|
|
347
|
+
if (!baseUrl) return;
|
|
348
|
+
const headers = options.writeKey ? { Authorization: `Bearer ${options.writeKey}` } : {};
|
|
349
|
+
void postJson(`${baseUrl}events/track_partial`, event, headers, {
|
|
350
|
+
maxAttempts: 1,
|
|
351
|
+
debug: (_a = options.debug) != null ? _a : false,
|
|
352
|
+
sdkName: options.sdkName
|
|
353
|
+
}).catch(() => {
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
var PROJECT_ID_HEADER = "X-Raindrop-Project-Id";
|
|
357
|
+
var PROJECT_ID_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
358
|
+
function isValidProjectIdSlug(value) {
|
|
359
|
+
return PROJECT_ID_SLUG_PATTERN.test(value);
|
|
360
|
+
}
|
|
361
|
+
function normalizeProjectId(raw, opts) {
|
|
362
|
+
if (typeof raw !== "string") return void 0;
|
|
363
|
+
const trimmed = raw.trim();
|
|
364
|
+
if (!trimmed) return void 0;
|
|
365
|
+
if (!isValidProjectIdSlug(trimmed) && opts.debug) {
|
|
366
|
+
console.warn(
|
|
367
|
+
`${opts.prefix} projectId "${trimmed}" does not match slug ${PROJECT_ID_SLUG_PATTERN.source}; sending anyway \u2014 backend may reject with HTTP 400`
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
return trimmed;
|
|
371
|
+
}
|
|
372
|
+
function projectIdHeaders(projectId) {
|
|
373
|
+
return projectId ? { [PROJECT_ID_HEADER]: projectId } : {};
|
|
374
|
+
}
|
|
375
|
+
var SHUTDOWN_DEADLINE_MS = 1e4;
|
|
376
|
+
var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
205
377
|
function mergePatches(target, source) {
|
|
206
378
|
var _a, _b, _c, _d;
|
|
207
379
|
const out = { ...target, ...source };
|
|
@@ -219,7 +391,8 @@ var EventShipper = class {
|
|
|
219
391
|
this.sticky = /* @__PURE__ */ new Map();
|
|
220
392
|
this.timers = /* @__PURE__ */ new Map();
|
|
221
393
|
this.inFlight = /* @__PURE__ */ new Set();
|
|
222
|
-
|
|
394
|
+
this.hasShutdown = false;
|
|
395
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
223
396
|
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
224
397
|
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
225
398
|
this.enabled = opts.enabled !== false;
|
|
@@ -228,11 +401,20 @@ var EventShipper = class {
|
|
|
228
401
|
this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
|
|
229
402
|
this.prefix = `[raindrop-ai/${this.sdkName}]`;
|
|
230
403
|
this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
|
|
404
|
+
this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
|
|
405
|
+
this.localDebuggerUrl = (_f = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _f : void 0;
|
|
406
|
+
if (this.debug && this.localDebuggerUrl) {
|
|
407
|
+
console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
|
|
408
|
+
}
|
|
409
|
+
this.projectId = normalizeProjectId(opts.projectId, {
|
|
410
|
+
debug: this.debug,
|
|
411
|
+
prefix: this.prefix
|
|
412
|
+
});
|
|
231
413
|
const isNode = typeof process !== "undefined" && typeof process.version === "string";
|
|
232
414
|
this.context = {
|
|
233
415
|
library: {
|
|
234
|
-
name: (
|
|
235
|
-
version: (
|
|
416
|
+
name: (_g = opts.libraryName) != null ? _g : "@raindrop-ai/core",
|
|
417
|
+
version: (_h = opts.libraryVersion) != null ? _h : "0.0.0"
|
|
236
418
|
},
|
|
237
419
|
metadata: {
|
|
238
420
|
jsRuntime: isNode ? "node" : "web",
|
|
@@ -246,10 +428,55 @@ var EventShipper = class {
|
|
|
246
428
|
authHeaders() {
|
|
247
429
|
return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
|
|
248
430
|
}
|
|
431
|
+
requestHeaders() {
|
|
432
|
+
return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Build the retry/timeout options for one POST, honoring the shutdown
|
|
436
|
+
* deadline. Returns `null` when the shutdown drain window is exhausted —
|
|
437
|
+
* the caller must drop the payload (with a rate-limited warning) instead
|
|
438
|
+
* of issuing a request that could outlive process exit.
|
|
439
|
+
*
|
|
440
|
+
* Checked fresh on EVERY send, so a shutdown that begins while the flush
|
|
441
|
+
* path is mid-drain takes effect immediately: no further retries, and the
|
|
442
|
+
* per-attempt timeout is clamped to the remaining window. After
|
|
443
|
+
* `shutdown()` returns (deadline cleared, `hasShutdown` still set),
|
|
444
|
+
* sends — late callers, or flush work the deadline abandoned mid-drain —
|
|
445
|
+
* run as a single short attempt rather than regaining the full retry
|
|
446
|
+
* schedule.
|
|
447
|
+
*/
|
|
448
|
+
requestOpts() {
|
|
449
|
+
if (this.shutdownDeadlineAt !== void 0) {
|
|
450
|
+
const remainingMs = this.shutdownDeadlineAt - Date.now();
|
|
451
|
+
if (remainingMs <= 0) return null;
|
|
452
|
+
return {
|
|
453
|
+
maxAttempts: 1,
|
|
454
|
+
debug: this.debug,
|
|
455
|
+
sdkName: this.sdkName,
|
|
456
|
+
timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
if (this.hasShutdown) {
|
|
460
|
+
return {
|
|
461
|
+
maxAttempts: 1,
|
|
462
|
+
debug: this.debug,
|
|
463
|
+
sdkName: this.sdkName,
|
|
464
|
+
timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
|
|
468
|
+
}
|
|
249
469
|
async patch(eventId, patch) {
|
|
250
470
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
251
471
|
if (!this.enabled) return;
|
|
252
472
|
if (!eventId || !eventId.trim()) return;
|
|
473
|
+
const maxChars = resolveMaxTextFieldChars(this.maxTextFieldCharsOpt);
|
|
474
|
+
if (typeof patch.input === "string" && patch.input.length > maxChars) {
|
|
475
|
+
patch = { ...patch, input: capText(patch.input, maxChars) };
|
|
476
|
+
}
|
|
477
|
+
if (typeof patch.output === "string" && patch.output.length > maxChars) {
|
|
478
|
+
patch = { ...patch, output: capText(patch.output, maxChars) };
|
|
479
|
+
}
|
|
253
480
|
if (this.debug) {
|
|
254
481
|
console.log(`${this.prefix} queue patch`, {
|
|
255
482
|
eventId,
|
|
@@ -296,9 +523,16 @@ var EventShipper = class {
|
|
|
296
523
|
})));
|
|
297
524
|
}
|
|
298
525
|
async shutdown() {
|
|
299
|
-
|
|
300
|
-
this.
|
|
301
|
-
|
|
526
|
+
this.hasShutdown = true;
|
|
527
|
+
this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
|
|
528
|
+
try {
|
|
529
|
+
for (const t of this.timers.values()) clearTimeout(t);
|
|
530
|
+
this.timers.clear();
|
|
531
|
+
const settled = await raceWithTimeout(this.flush(), SHUTDOWN_DEADLINE_MS);
|
|
532
|
+
if (!settled) this.warnShutdownDrop("in-flight request(s) at shutdown");
|
|
533
|
+
} finally {
|
|
534
|
+
this.shutdownDeadlineAt = void 0;
|
|
535
|
+
}
|
|
302
536
|
}
|
|
303
537
|
async trackSignal(signal) {
|
|
304
538
|
var _a, _b;
|
|
@@ -318,16 +552,21 @@ var EventShipper = class {
|
|
|
318
552
|
}
|
|
319
553
|
}
|
|
320
554
|
];
|
|
555
|
+
if (!this.writeKey) return;
|
|
321
556
|
const url = `${this.baseUrl}signals/track`;
|
|
557
|
+
const opts = this.requestOpts();
|
|
558
|
+
if (!opts) {
|
|
559
|
+
this.warnShutdownDrop("signal");
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
322
562
|
try {
|
|
323
|
-
await postJson(url, body, this.
|
|
324
|
-
maxAttempts: 3,
|
|
325
|
-
debug: this.debug,
|
|
326
|
-
sdkName: this.sdkName
|
|
327
|
-
});
|
|
563
|
+
await postJson(url, body, this.requestHeaders(), opts);
|
|
328
564
|
} catch (err) {
|
|
329
565
|
const msg = err instanceof Error ? err.message : String(err);
|
|
330
|
-
|
|
566
|
+
rateLimitedLog(
|
|
567
|
+
`${this.prefix}.send_signal_failed`,
|
|
568
|
+
() => console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`)
|
|
569
|
+
);
|
|
331
570
|
}
|
|
332
571
|
}
|
|
333
572
|
async identify(users) {
|
|
@@ -348,19 +587,30 @@ var EventShipper = class {
|
|
|
348
587
|
traits: (_a = user.traits) != null ? _a : {}
|
|
349
588
|
};
|
|
350
589
|
});
|
|
590
|
+
if (!this.writeKey) return;
|
|
351
591
|
if (body.length === 0) return;
|
|
352
592
|
const url = `${this.baseUrl}users/identify`;
|
|
593
|
+
const opts = this.requestOpts();
|
|
594
|
+
if (!opts) {
|
|
595
|
+
this.warnShutdownDrop("identify");
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
353
598
|
try {
|
|
354
|
-
await postJson(url, body, this.
|
|
355
|
-
maxAttempts: 3,
|
|
356
|
-
debug: this.debug,
|
|
357
|
-
sdkName: this.sdkName
|
|
358
|
-
});
|
|
599
|
+
await postJson(url, body, this.requestHeaders(), opts);
|
|
359
600
|
} catch (err) {
|
|
360
601
|
const msg = err instanceof Error ? err.message : String(err);
|
|
361
|
-
|
|
602
|
+
rateLimitedLog(
|
|
603
|
+
`${this.prefix}.send_identify_failed`,
|
|
604
|
+
() => console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`)
|
|
605
|
+
);
|
|
362
606
|
}
|
|
363
607
|
}
|
|
608
|
+
warnShutdownDrop(what) {
|
|
609
|
+
rateLimitedLog(
|
|
610
|
+
`${this.prefix}.shutdown_deadline`,
|
|
611
|
+
() => console.warn(`${this.prefix} shutdown flush deadline exceeded; dropping ${what}`)
|
|
612
|
+
);
|
|
613
|
+
}
|
|
364
614
|
async flushOne(eventId) {
|
|
365
615
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
366
616
|
if (!this.enabled) return;
|
|
@@ -424,11 +674,25 @@ var EventShipper = class {
|
|
|
424
674
|
endpoint: url
|
|
425
675
|
});
|
|
426
676
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
677
|
+
if (this.localDebuggerUrl) {
|
|
678
|
+
mirrorPartialEventToLocalDebugger(payload, {
|
|
679
|
+
baseUrl: this.localDebuggerUrl,
|
|
680
|
+
writeKey: this.writeKey,
|
|
681
|
+
debug: this.debug,
|
|
682
|
+
sdkName: this.sdkName
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
if (!this.writeKey) {
|
|
686
|
+
if (!isPending) this.sticky.delete(eventId);
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
const opts = this.requestOpts();
|
|
690
|
+
if (!opts) {
|
|
691
|
+
this.warnShutdownDrop(`track_partial ${eventId}`);
|
|
692
|
+
if (!isPending) this.sticky.delete(eventId);
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
const p = postJson(url, payload, this.requestHeaders(), opts);
|
|
432
696
|
this.inFlight.add(p);
|
|
433
697
|
try {
|
|
434
698
|
try {
|
|
@@ -438,7 +702,10 @@ var EventShipper = class {
|
|
|
438
702
|
}
|
|
439
703
|
} catch (err) {
|
|
440
704
|
const msg = err instanceof Error ? err.message : String(err);
|
|
441
|
-
|
|
705
|
+
rateLimitedLog(
|
|
706
|
+
`${this.prefix}.send_track_partial_failed`,
|
|
707
|
+
() => console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`)
|
|
708
|
+
);
|
|
442
709
|
}
|
|
443
710
|
} finally {
|
|
444
711
|
this.inFlight.delete(p);
|
|
@@ -448,28 +715,114 @@ var EventShipper = class {
|
|
|
448
715
|
}
|
|
449
716
|
}
|
|
450
717
|
};
|
|
451
|
-
var
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
718
|
+
var DEFAULT_SECRET_KEY_NAMES = [
|
|
719
|
+
"apikey",
|
|
720
|
+
"apisecret",
|
|
721
|
+
"apitoken",
|
|
722
|
+
"secretaccesskey",
|
|
723
|
+
"sessiontoken",
|
|
724
|
+
"privatekey",
|
|
725
|
+
"privatekeyid",
|
|
726
|
+
"clientsecret",
|
|
727
|
+
"accesstoken",
|
|
728
|
+
"refreshtoken",
|
|
729
|
+
"oauthtoken",
|
|
730
|
+
"bearertoken",
|
|
731
|
+
"authorization",
|
|
732
|
+
"password",
|
|
733
|
+
"passphrase"
|
|
734
|
+
];
|
|
735
|
+
var REDACTED_PLACEHOLDER = "[REDACTED]";
|
|
736
|
+
function normalizeKeyName(name) {
|
|
737
|
+
return name.toLowerCase().replace(/[-_.]/g, "");
|
|
456
738
|
}
|
|
457
|
-
function
|
|
458
|
-
var _a;
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
739
|
+
function redactSecretsInObject(value, options) {
|
|
740
|
+
var _a, _b;
|
|
741
|
+
const normalizedSecretSet = buildSecretSet((_a = options == null ? void 0 : options.secretKeyNames) != null ? _a : DEFAULT_SECRET_KEY_NAMES);
|
|
742
|
+
const placeholder = (_b = options == null ? void 0 : options.placeholder) != null ? _b : REDACTED_PLACEHOLDER;
|
|
743
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
744
|
+
const walk = (node) => {
|
|
745
|
+
if (node === null || typeof node !== "object") return node;
|
|
746
|
+
if (seen.has(node)) return "[CIRCULAR]";
|
|
747
|
+
seen.add(node);
|
|
748
|
+
if (Array.isArray(node)) {
|
|
749
|
+
return node.map((item) => walk(item));
|
|
750
|
+
}
|
|
751
|
+
const out = {};
|
|
752
|
+
for (const [k, v] of Object.entries(node)) {
|
|
753
|
+
if (normalizedSecretSet.has(normalizeKeyName(k))) {
|
|
754
|
+
out[k] = placeholder;
|
|
755
|
+
} else {
|
|
756
|
+
out[k] = walk(v);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
return out;
|
|
760
|
+
};
|
|
761
|
+
return walk(value);
|
|
762
|
+
}
|
|
763
|
+
function buildSecretSet(names) {
|
|
764
|
+
const set = /* @__PURE__ */ new Set();
|
|
765
|
+
for (const name of names) set.add(normalizeKeyName(name));
|
|
766
|
+
return set;
|
|
767
|
+
}
|
|
768
|
+
var DEFAULT_REDACT_ATTRIBUTE_KEYS = [
|
|
769
|
+
"ai.request.providerOptions",
|
|
770
|
+
"ai.response.providerMetadata"
|
|
771
|
+
];
|
|
772
|
+
function defaultTransformSpan(span) {
|
|
773
|
+
const attrs = span.attributes;
|
|
774
|
+
if (!attrs || attrs.length === 0) return span;
|
|
775
|
+
let nextAttrs;
|
|
776
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
777
|
+
const attr = attrs[i];
|
|
778
|
+
const redacted = redactJsonAttributeValue(attr.key, attr.value);
|
|
779
|
+
if (redacted === void 0) continue;
|
|
780
|
+
if (!nextAttrs) nextAttrs = attrs.slice();
|
|
781
|
+
nextAttrs[i] = { key: attr.key, value: redacted };
|
|
782
|
+
}
|
|
783
|
+
if (!nextAttrs) return span;
|
|
784
|
+
return { ...span, attributes: nextAttrs };
|
|
785
|
+
}
|
|
786
|
+
var REDACT_JSON_ATTRIBUTE_KEYS = new Set(DEFAULT_REDACT_ATTRIBUTE_KEYS);
|
|
787
|
+
function redactJsonAttributeValue(key, value) {
|
|
788
|
+
if (!REDACT_JSON_ATTRIBUTE_KEYS.has(key)) return void 0;
|
|
789
|
+
const json = value.stringValue;
|
|
790
|
+
if (typeof json !== "string" || json.length === 0) return void 0;
|
|
791
|
+
let parsed;
|
|
792
|
+
try {
|
|
793
|
+
parsed = JSON.parse(json);
|
|
794
|
+
} catch (e) {
|
|
795
|
+
return void 0;
|
|
796
|
+
}
|
|
797
|
+
const scrubbed = redactSecretsInObject(parsed);
|
|
798
|
+
let scrubbedJson;
|
|
799
|
+
try {
|
|
800
|
+
scrubbedJson = JSON.stringify(scrubbed);
|
|
801
|
+
} catch (e) {
|
|
802
|
+
return void 0;
|
|
803
|
+
}
|
|
804
|
+
if (scrubbedJson === json) return void 0;
|
|
805
|
+
return { stringValue: scrubbedJson };
|
|
806
|
+
}
|
|
807
|
+
function applyOtelSpanAttributeLimit(limit) {
|
|
808
|
+
var _a, _b;
|
|
809
|
+
try {
|
|
810
|
+
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;
|
|
811
|
+
if (!raw) return limit;
|
|
812
|
+
const parsed = Number.parseInt(raw, 10);
|
|
813
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
814
|
+
return Math.min(limit, parsed);
|
|
815
|
+
}
|
|
816
|
+
} catch (e) {
|
|
817
|
+
}
|
|
818
|
+
return limit;
|
|
467
819
|
}
|
|
468
820
|
var TraceShipper = class {
|
|
469
821
|
constructor(opts) {
|
|
470
822
|
this.queue = [];
|
|
471
823
|
this.inFlight = /* @__PURE__ */ new Set();
|
|
472
|
-
|
|
824
|
+
this.hasShutdown = false;
|
|
825
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
473
826
|
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
474
827
|
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
475
828
|
this.enabled = opts.enabled !== false;
|
|
@@ -482,13 +835,83 @@ var TraceShipper = class {
|
|
|
482
835
|
this.prefix = `[raindrop-ai/${this.sdkName}]`;
|
|
483
836
|
this.serviceName = (_g = opts.serviceName) != null ? _g : "raindrop.core";
|
|
484
837
|
this.serviceVersion = (_h = opts.serviceVersion) != null ? _h : "0.0.0";
|
|
485
|
-
|
|
486
|
-
if (
|
|
487
|
-
this.
|
|
488
|
-
|
|
489
|
-
|
|
838
|
+
this.localDebuggerUrl = (_i = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _i : void 0;
|
|
839
|
+
if (this.debug && this.localDebuggerUrl) {
|
|
840
|
+
console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
|
|
841
|
+
}
|
|
842
|
+
this.projectId = normalizeProjectId(opts.projectId, {
|
|
843
|
+
debug: this.debug,
|
|
844
|
+
prefix: this.prefix
|
|
845
|
+
});
|
|
846
|
+
this.transformSpanHook = opts.transformSpan;
|
|
847
|
+
this.disableDefaultRedaction = opts.disableDefaultRedaction === true;
|
|
848
|
+
this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Cap every string attribute value on the span. O(#attributes) length
|
|
852
|
+
* checks; only oversized values pay a slice. Runs AFTER the redaction
|
|
853
|
+
* pipeline so the default secret-scrub still sees parseable JSON in
|
|
854
|
+
* `ai.request.providerOptions` / `ai.response.providerMetadata` (capping
|
|
855
|
+
* first could cut a JSON blob mid-way, fail the parse, and ship secrets
|
|
856
|
+
* in the surviving prefix).
|
|
857
|
+
*
|
|
858
|
+
* A stricter `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var is honored
|
|
859
|
+
* for span content, matching the Python SDK and the OTel SDK convention.
|
|
860
|
+
*/
|
|
861
|
+
capSpanAttributes(span) {
|
|
862
|
+
var _a;
|
|
863
|
+
const maxChars = applyOtelSpanAttributeLimit(
|
|
864
|
+
resolveMaxTextFieldChars(this.maxTextFieldCharsOpt)
|
|
865
|
+
);
|
|
866
|
+
const attrs = span.attributes;
|
|
867
|
+
if (!attrs || attrs.length === 0) return span;
|
|
868
|
+
let nextAttrs;
|
|
869
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
870
|
+
const attr = attrs[i];
|
|
871
|
+
const value = (_a = attr.value) == null ? void 0 : _a.stringValue;
|
|
872
|
+
if (typeof value !== "string" || value.length <= maxChars) continue;
|
|
873
|
+
if (!nextAttrs) nextAttrs = attrs.slice();
|
|
874
|
+
nextAttrs[i] = {
|
|
875
|
+
key: attr.key,
|
|
876
|
+
value: { ...attr.value, stringValue: capText(value, maxChars) }
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
if (!nextAttrs) return span;
|
|
880
|
+
return { ...span, attributes: nextAttrs };
|
|
881
|
+
}
|
|
882
|
+
/**
|
|
883
|
+
* Apply the user `transformSpan` hook (if any) followed by the default
|
|
884
|
+
* redactor (unless disabled). Returns either the (possibly new) span to
|
|
885
|
+
* ship, or `null` to drop the span entirely.
|
|
886
|
+
*
|
|
887
|
+
* Ordering: user hook runs first so callers can rewrite the span freely
|
|
888
|
+
* (rename attrs, add new ones, scrub things the default doesn't know
|
|
889
|
+
* about). The default redactor then runs on whatever the user produced,
|
|
890
|
+
* acting as the always-on floor for documented BYOK secrets. If the user
|
|
891
|
+
* sets `disableDefaultRedaction: true`, the floor is skipped.
|
|
892
|
+
*
|
|
893
|
+
* Fail-closed: if the user hook throws, the span is dropped — a buggy
|
|
894
|
+
* hook can never accidentally ship raw, un-redacted spans.
|
|
895
|
+
*/
|
|
896
|
+
redactSpan(span) {
|
|
897
|
+
let current = span;
|
|
898
|
+
if (this.transformSpanHook) {
|
|
899
|
+
try {
|
|
900
|
+
const result = this.transformSpanHook(current);
|
|
901
|
+
if (result === null) return null;
|
|
902
|
+
if (result !== void 0) current = result;
|
|
903
|
+
} catch (err) {
|
|
904
|
+
if (this.debug) {
|
|
905
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
906
|
+
console.warn(`${this.prefix} transformSpan hook threw: ${msg}`);
|
|
907
|
+
}
|
|
908
|
+
return null;
|
|
490
909
|
}
|
|
491
910
|
}
|
|
911
|
+
if (!this.disableDefaultRedaction) {
|
|
912
|
+
current = defaultTransformSpan(current);
|
|
913
|
+
}
|
|
914
|
+
return this.capSpanAttributes(current);
|
|
492
915
|
}
|
|
493
916
|
isDebugEnabled() {
|
|
494
917
|
return this.debug;
|
|
@@ -496,6 +919,9 @@ var TraceShipper = class {
|
|
|
496
919
|
authHeaders() {
|
|
497
920
|
return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
|
|
498
921
|
}
|
|
922
|
+
requestHeaders() {
|
|
923
|
+
return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
|
|
924
|
+
}
|
|
499
925
|
startSpan(args) {
|
|
500
926
|
var _a, _b;
|
|
501
927
|
const ids = createSpanIds(args.parent);
|
|
@@ -506,8 +932,8 @@ var TraceShipper = class {
|
|
|
506
932
|
];
|
|
507
933
|
if ((_b = args.attributes) == null ? void 0 : _b.length) attrs.push(...args.attributes);
|
|
508
934
|
const span = { ids, name: args.name, startTimeUnixNano: started, attributes: attrs };
|
|
509
|
-
|
|
510
|
-
|
|
935
|
+
this.mirrorToLocalDebugger(
|
|
936
|
+
buildOtlpSpan({
|
|
511
937
|
ids: span.ids,
|
|
512
938
|
name: span.name,
|
|
513
939
|
startTimeUnixNano: span.startTimeUnixNano,
|
|
@@ -515,16 +941,21 @@ var TraceShipper = class {
|
|
|
515
941
|
// placeholder — will be updated on endSpan
|
|
516
942
|
attributes: span.attributes,
|
|
517
943
|
status: { code: SpanStatusCode.UNSET }
|
|
518
|
-
})
|
|
519
|
-
|
|
520
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
521
|
-
baseUrl: this.localDebuggerUrl,
|
|
522
|
-
debug: false,
|
|
523
|
-
sdkName: this.sdkName
|
|
524
|
-
});
|
|
525
|
-
}
|
|
944
|
+
})
|
|
945
|
+
);
|
|
526
946
|
return span;
|
|
527
947
|
}
|
|
948
|
+
mirrorToLocalDebugger(span) {
|
|
949
|
+
if (!this.localDebuggerUrl) return;
|
|
950
|
+
const redacted = this.redactSpan(span);
|
|
951
|
+
if (redacted === null) return;
|
|
952
|
+
const body = buildExportTraceServiceRequest([redacted], this.serviceName, this.serviceVersion);
|
|
953
|
+
mirrorTraceExportToLocalDebugger(body, {
|
|
954
|
+
baseUrl: this.localDebuggerUrl,
|
|
955
|
+
debug: false,
|
|
956
|
+
sdkName: this.sdkName
|
|
957
|
+
});
|
|
958
|
+
}
|
|
528
959
|
endSpan(span, extra) {
|
|
529
960
|
var _a, _b;
|
|
530
961
|
if (span.endTimeUnixNano) return;
|
|
@@ -546,14 +977,7 @@ var TraceShipper = class {
|
|
|
546
977
|
status
|
|
547
978
|
});
|
|
548
979
|
this.enqueue(otlp);
|
|
549
|
-
|
|
550
|
-
const body = buildExportTraceServiceRequest([otlp], this.serviceName, this.serviceVersion);
|
|
551
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
552
|
-
baseUrl: this.localDebuggerUrl,
|
|
553
|
-
debug: false,
|
|
554
|
-
sdkName: this.sdkName
|
|
555
|
-
});
|
|
556
|
-
}
|
|
980
|
+
this.mirrorToLocalDebugger(otlp);
|
|
557
981
|
}
|
|
558
982
|
createSpan(args) {
|
|
559
983
|
var _a;
|
|
@@ -571,14 +995,7 @@ var TraceShipper = class {
|
|
|
571
995
|
status: args.status
|
|
572
996
|
});
|
|
573
997
|
this.enqueue(otlp);
|
|
574
|
-
|
|
575
|
-
const body = buildExportTraceServiceRequest([otlp], this.serviceName, this.serviceVersion);
|
|
576
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
577
|
-
baseUrl: this.localDebuggerUrl,
|
|
578
|
-
debug: false,
|
|
579
|
-
sdkName: this.sdkName
|
|
580
|
-
});
|
|
581
|
-
}
|
|
998
|
+
this.mirrorToLocalDebugger(otlp);
|
|
582
999
|
}
|
|
583
1000
|
enqueue(span) {
|
|
584
1001
|
if (!this.enabled) return;
|
|
@@ -590,10 +1007,12 @@ var TraceShipper = class {
|
|
|
590
1007
|
)}`
|
|
591
1008
|
);
|
|
592
1009
|
}
|
|
1010
|
+
const redacted = this.redactSpan(span);
|
|
1011
|
+
if (redacted === null) return;
|
|
593
1012
|
if (this.queue.length >= this.maxQueueSize) {
|
|
594
1013
|
this.queue.shift();
|
|
595
1014
|
}
|
|
596
|
-
this.queue.push(
|
|
1015
|
+
this.queue.push(redacted);
|
|
597
1016
|
if (this.queue.length >= this.maxBatchSize) {
|
|
598
1017
|
void this.flush().catch(() => {
|
|
599
1018
|
});
|
|
@@ -615,6 +1034,17 @@ var TraceShipper = class {
|
|
|
615
1034
|
}
|
|
616
1035
|
while (this.queue.length > 0) {
|
|
617
1036
|
const batch = this.queue.splice(0, this.maxBatchSize);
|
|
1037
|
+
if (!this.writeKey) continue;
|
|
1038
|
+
const opts = this.requestOpts();
|
|
1039
|
+
if (!opts) {
|
|
1040
|
+
rateLimitedLog(
|
|
1041
|
+
`${this.prefix}.shutdown_deadline`,
|
|
1042
|
+
() => console.warn(
|
|
1043
|
+
`${this.prefix} shutdown flush deadline exceeded; dropping ${batch.length} spans`
|
|
1044
|
+
)
|
|
1045
|
+
);
|
|
1046
|
+
continue;
|
|
1047
|
+
}
|
|
618
1048
|
const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
|
|
619
1049
|
const url = `${this.baseUrl}traces`;
|
|
620
1050
|
if (this.debug) {
|
|
@@ -623,11 +1053,7 @@ var TraceShipper = class {
|
|
|
623
1053
|
endpoint: url
|
|
624
1054
|
});
|
|
625
1055
|
}
|
|
626
|
-
const p = postJson(url, body, this.
|
|
627
|
-
maxAttempts: 3,
|
|
628
|
-
debug: this.debug,
|
|
629
|
-
sdkName: this.sdkName
|
|
630
|
-
});
|
|
1056
|
+
const p = postJson(url, body, this.requestHeaders(), opts);
|
|
631
1057
|
this.inFlight.add(p);
|
|
632
1058
|
try {
|
|
633
1059
|
try {
|
|
@@ -635,21 +1061,61 @@ var TraceShipper = class {
|
|
|
635
1061
|
if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
|
|
636
1062
|
} catch (err) {
|
|
637
1063
|
const msg = err instanceof Error ? err.message : String(err);
|
|
638
|
-
|
|
1064
|
+
rateLimitedLog(
|
|
1065
|
+
`${this.prefix}.send_spans_failed`,
|
|
1066
|
+
() => console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`)
|
|
1067
|
+
);
|
|
639
1068
|
}
|
|
640
1069
|
} finally {
|
|
641
1070
|
this.inFlight.delete(p);
|
|
642
1071
|
}
|
|
643
1072
|
}
|
|
644
1073
|
}
|
|
1074
|
+
/** See EventShipper.requestOpts — same shutdown-budget semantics. */
|
|
1075
|
+
requestOpts() {
|
|
1076
|
+
if (this.shutdownDeadlineAt !== void 0) {
|
|
1077
|
+
const remainingMs = this.shutdownDeadlineAt - Date.now();
|
|
1078
|
+
if (remainingMs <= 0) return null;
|
|
1079
|
+
return {
|
|
1080
|
+
maxAttempts: 1,
|
|
1081
|
+
debug: this.debug,
|
|
1082
|
+
sdkName: this.sdkName,
|
|
1083
|
+
timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
|
|
1084
|
+
};
|
|
1085
|
+
}
|
|
1086
|
+
if (this.hasShutdown) {
|
|
1087
|
+
return {
|
|
1088
|
+
maxAttempts: 1,
|
|
1089
|
+
debug: this.debug,
|
|
1090
|
+
sdkName: this.sdkName,
|
|
1091
|
+
timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
|
|
1095
|
+
}
|
|
645
1096
|
async shutdown() {
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
1097
|
+
this.hasShutdown = true;
|
|
1098
|
+
this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
|
|
1099
|
+
try {
|
|
1100
|
+
if (this.timer) {
|
|
1101
|
+
clearTimeout(this.timer);
|
|
1102
|
+
this.timer = void 0;
|
|
1103
|
+
}
|
|
1104
|
+
const drain = async () => {
|
|
1105
|
+
await this.flush();
|
|
1106
|
+
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
1107
|
+
})));
|
|
1108
|
+
};
|
|
1109
|
+
const settled = await raceWithTimeout(drain(), SHUTDOWN_DEADLINE_MS);
|
|
1110
|
+
if (!settled) {
|
|
1111
|
+
rateLimitedLog(
|
|
1112
|
+
`${this.prefix}.shutdown_deadline`,
|
|
1113
|
+
() => console.warn(`${this.prefix} shutdown flush deadline exceeded; abandoning in-flight spans`)
|
|
1114
|
+
);
|
|
1115
|
+
}
|
|
1116
|
+
} finally {
|
|
1117
|
+
this.shutdownDeadlineAt = void 0;
|
|
649
1118
|
}
|
|
650
|
-
await this.flush();
|
|
651
|
-
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
652
|
-
})));
|
|
653
1119
|
}
|
|
654
1120
|
};
|
|
655
1121
|
|
|
@@ -657,6 +1123,133 @@ var TraceShipper = class {
|
|
|
657
1123
|
import { AsyncLocalStorage } from "async_hooks";
|
|
658
1124
|
globalThis.RAINDROP_ASYNC_LOCAL_STORAGE = AsyncLocalStorage;
|
|
659
1125
|
|
|
1126
|
+
// src/truncation.ts
|
|
1127
|
+
var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
|
|
1128
|
+
var DEFAULT_MAX_TEXT_FIELD_CHARS2 = 1e6;
|
|
1129
|
+
var MAX_DEPTH = 12;
|
|
1130
|
+
var configuredMaxTextFieldChars;
|
|
1131
|
+
function setMaxTextFieldChars(limit) {
|
|
1132
|
+
if (limit === void 0) return;
|
|
1133
|
+
if (typeof limit === "number" && Number.isFinite(limit) && limit > 0) {
|
|
1134
|
+
configuredMaxTextFieldChars = Math.floor(limit);
|
|
1135
|
+
} else {
|
|
1136
|
+
console.warn(`[raindrop-ai] maxTextFieldChars=${String(limit)} ignored; must be > 0`);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
function otelEnvLimit() {
|
|
1140
|
+
var _a;
|
|
1141
|
+
if (typeof process === "undefined") return void 0;
|
|
1142
|
+
const raw = (_a = process.env) == null ? void 0 : _a.OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT;
|
|
1143
|
+
if (!raw) return void 0;
|
|
1144
|
+
const parsed = Number.parseInt(raw, 10);
|
|
1145
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
|
|
1146
|
+
}
|
|
1147
|
+
function effectiveMaxTextFieldChars() {
|
|
1148
|
+
const base = configuredMaxTextFieldChars != null ? configuredMaxTextFieldChars : DEFAULT_MAX_TEXT_FIELD_CHARS2;
|
|
1149
|
+
const env = otelEnvLimit();
|
|
1150
|
+
return env !== void 0 && env < base ? env : base;
|
|
1151
|
+
}
|
|
1152
|
+
function truncateToLimit2(text, limit) {
|
|
1153
|
+
if (limit > TRUNCATION_MARKER2.length) {
|
|
1154
|
+
return text.slice(0, limit - TRUNCATION_MARKER2.length) + TRUNCATION_MARKER2;
|
|
1155
|
+
}
|
|
1156
|
+
return text.slice(0, limit);
|
|
1157
|
+
}
|
|
1158
|
+
function capText2(value, limit) {
|
|
1159
|
+
if (typeof value !== "string") return value;
|
|
1160
|
+
const max = limit != null ? limit : effectiveMaxTextFieldChars();
|
|
1161
|
+
if (value.length <= max) return value;
|
|
1162
|
+
return truncateToLimit2(value, max);
|
|
1163
|
+
}
|
|
1164
|
+
function boundedClone2(value, budget, depth) {
|
|
1165
|
+
var _a, _b;
|
|
1166
|
+
if (budget.remaining <= 0) return TRUNCATION_MARKER2;
|
|
1167
|
+
if (typeof value === "string") {
|
|
1168
|
+
if (value.length > budget.remaining) {
|
|
1169
|
+
const taken = value.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
|
|
1170
|
+
budget.remaining = 0;
|
|
1171
|
+
return taken;
|
|
1172
|
+
}
|
|
1173
|
+
budget.remaining -= Math.max(value.length, 1);
|
|
1174
|
+
return value;
|
|
1175
|
+
}
|
|
1176
|
+
if (value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
1177
|
+
budget.remaining -= 8;
|
|
1178
|
+
return value;
|
|
1179
|
+
}
|
|
1180
|
+
if (typeof value !== "object") {
|
|
1181
|
+
budget.remaining -= 1;
|
|
1182
|
+
return value;
|
|
1183
|
+
}
|
|
1184
|
+
if (value instanceof Uint8Array) {
|
|
1185
|
+
const maxBytes = Math.max(0, Math.ceil(budget.remaining * 3 / 4));
|
|
1186
|
+
if (value.byteLength > maxBytes) {
|
|
1187
|
+
const taken = base64Encode(value.subarray(0, maxBytes)) + TRUNCATION_MARKER2;
|
|
1188
|
+
budget.remaining = 0;
|
|
1189
|
+
return taken;
|
|
1190
|
+
}
|
|
1191
|
+
const encoded = base64Encode(value);
|
|
1192
|
+
budget.remaining -= Math.max(encoded.length, 1);
|
|
1193
|
+
return encoded;
|
|
1194
|
+
}
|
|
1195
|
+
if (depth >= MAX_DEPTH) {
|
|
1196
|
+
budget.remaining -= 16;
|
|
1197
|
+
const name = (_b = (_a = value.constructor) == null ? void 0 : _a.name) != null ? _b : "object";
|
|
1198
|
+
return `<max depth: ${name}>`;
|
|
1199
|
+
}
|
|
1200
|
+
const toJSON = value.toJSON;
|
|
1201
|
+
if (typeof toJSON === "function") {
|
|
1202
|
+
budget.remaining -= 2;
|
|
1203
|
+
return boundedClone2(toJSON.call(value), budget, depth + 1);
|
|
1204
|
+
}
|
|
1205
|
+
if (Array.isArray(value)) {
|
|
1206
|
+
budget.remaining -= 2;
|
|
1207
|
+
const out2 = [];
|
|
1208
|
+
for (const item of value) {
|
|
1209
|
+
if (budget.remaining <= 0) {
|
|
1210
|
+
out2.push(TRUNCATION_MARKER2);
|
|
1211
|
+
break;
|
|
1212
|
+
}
|
|
1213
|
+
out2.push(boundedClone2(item, budget, depth + 1));
|
|
1214
|
+
}
|
|
1215
|
+
return out2;
|
|
1216
|
+
}
|
|
1217
|
+
budget.remaining -= 2;
|
|
1218
|
+
const out = {};
|
|
1219
|
+
for (const key in value) {
|
|
1220
|
+
if (!Object.prototype.hasOwnProperty.call(value, key)) continue;
|
|
1221
|
+
if (budget.remaining <= 0) {
|
|
1222
|
+
out["..."] = TRUNCATION_MARKER2;
|
|
1223
|
+
break;
|
|
1224
|
+
}
|
|
1225
|
+
let cappedKey = key;
|
|
1226
|
+
if (key.length > budget.remaining) {
|
|
1227
|
+
cappedKey = key.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
|
|
1228
|
+
budget.remaining = 0;
|
|
1229
|
+
out[cappedKey] = TRUNCATION_MARKER2;
|
|
1230
|
+
break;
|
|
1231
|
+
}
|
|
1232
|
+
budget.remaining -= Math.max(key.length, 1);
|
|
1233
|
+
out[cappedKey] = boundedClone2(value[key], budget, depth + 1);
|
|
1234
|
+
}
|
|
1235
|
+
return out;
|
|
1236
|
+
}
|
|
1237
|
+
function boundedStringify(value, limit) {
|
|
1238
|
+
const max = limit != null ? limit : effectiveMaxTextFieldChars();
|
|
1239
|
+
try {
|
|
1240
|
+
if (typeof value === "string") {
|
|
1241
|
+
const text2 = JSON.stringify(capText2(value, max));
|
|
1242
|
+
return text2.length <= max ? text2 : truncateToLimit2(text2, max);
|
|
1243
|
+
}
|
|
1244
|
+
const pruned = boundedClone2(value, { remaining: max + TRUNCATION_MARKER2.length + 256 }, 0);
|
|
1245
|
+
const text = JSON.stringify(pruned);
|
|
1246
|
+
if (text === void 0) return void 0;
|
|
1247
|
+
return text.length <= max ? text : truncateToLimit2(text, max);
|
|
1248
|
+
} catch (e) {
|
|
1249
|
+
return void 0;
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
|
|
660
1253
|
// src/callback-handler.ts
|
|
661
1254
|
var LANGGRAPH_INTERNAL_NAMES = /* @__PURE__ */ new Set([
|
|
662
1255
|
"LangGraph",
|
|
@@ -746,7 +1339,7 @@ var RaindropCallbackHandler = class extends BaseCallbackHandler {
|
|
|
746
1339
|
await this.eventShipper.patch(eventId, {
|
|
747
1340
|
userId: this.userId,
|
|
748
1341
|
convoId: this.convoId,
|
|
749
|
-
input: prompts.join("\n"),
|
|
1342
|
+
input: capText2(prompts.join("\n")),
|
|
750
1343
|
properties: buildProperties(tags, metadata),
|
|
751
1344
|
isPending: true
|
|
752
1345
|
});
|
|
@@ -776,7 +1369,7 @@ var RaindropCallbackHandler = class extends BaseCallbackHandler {
|
|
|
776
1369
|
return role === "human";
|
|
777
1370
|
});
|
|
778
1371
|
const lastMsg = userMessages.length > 0 ? userMessages[userMessages.length - 1] : allMessages[allMessages.length - 1];
|
|
779
|
-
const inputText = lastMsg ? typeof lastMsg.content === "string" ? lastMsg.content : safeStringify(lastMsg.content) : void 0;
|
|
1372
|
+
const inputText = lastMsg ? typeof lastMsg.content === "string" ? capText2(lastMsg.content) : safeStringify(lastMsg.content) : void 0;
|
|
780
1373
|
await this.eventShipper.patch(eventId, {
|
|
781
1374
|
userId: this.userId,
|
|
782
1375
|
convoId: this.convoId,
|
|
@@ -806,7 +1399,7 @@ var RaindropCallbackHandler = class extends BaseCallbackHandler {
|
|
|
806
1399
|
if (promptTokens != null) properties["ai.usage.prompt_tokens"] = promptTokens;
|
|
807
1400
|
if (completionTokens != null) properties["ai.usage.completion_tokens"] = completionTokens;
|
|
808
1401
|
await this.finalizeEventIfRoot(runId, {
|
|
809
|
-
output: outputText,
|
|
1402
|
+
output: capText2(outputText),
|
|
810
1403
|
model,
|
|
811
1404
|
properties: Object.keys(properties).length > 0 ? properties : void 0
|
|
812
1405
|
});
|
|
@@ -899,7 +1492,7 @@ var RaindropCallbackHandler = class extends BaseCallbackHandler {
|
|
|
899
1492
|
try {
|
|
900
1493
|
const span = this.spans.get(runId);
|
|
901
1494
|
if (span) {
|
|
902
|
-
const outputStr = typeof output === "string" ? output : safeStringify(output);
|
|
1495
|
+
const outputStr = typeof output === "string" ? output : safeStringify(output, 4096);
|
|
903
1496
|
this.traceShipper.endSpan(span, {
|
|
904
1497
|
attributes: [attrString("tool.output", truncate(outputStr, 4096))]
|
|
905
1498
|
});
|
|
@@ -984,7 +1577,7 @@ var RaindropCallbackHandler = class extends BaseCallbackHandler {
|
|
|
984
1577
|
attributes: [
|
|
985
1578
|
attrString("agent.tool", action.tool),
|
|
986
1579
|
attrString("agent.tool_input", truncate(
|
|
987
|
-
typeof action.toolInput === "string" ? action.toolInput : safeStringify(action.toolInput),
|
|
1580
|
+
typeof action.toolInput === "string" ? action.toolInput : safeStringify(action.toolInput, 4096),
|
|
988
1581
|
4096
|
|
989
1582
|
))
|
|
990
1583
|
]
|
|
@@ -1026,13 +1619,10 @@ function buildProperties(tags, metadata) {
|
|
|
1026
1619
|
if (metadata) Object.assign(props, metadata);
|
|
1027
1620
|
return props;
|
|
1028
1621
|
}
|
|
1029
|
-
function safeStringify(value) {
|
|
1622
|
+
function safeStringify(value, limit) {
|
|
1030
1623
|
var _a;
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
} catch (e) {
|
|
1034
|
-
return String(value);
|
|
1035
|
-
}
|
|
1624
|
+
if (value === void 0) return "";
|
|
1625
|
+
return (_a = boundedStringify(value, limit)) != null ? _a : String(value);
|
|
1036
1626
|
}
|
|
1037
1627
|
function truncate(str, maxLen) {
|
|
1038
1628
|
if (str.length <= maxLen) return str;
|
|
@@ -1051,14 +1641,14 @@ function extractLLMMetadata(output) {
|
|
|
1051
1641
|
const usage = (_g = usageMeta != null ? usageMeta : tokenUsage) != null ? _g : estimatedTokens;
|
|
1052
1642
|
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;
|
|
1053
1643
|
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;
|
|
1054
|
-
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) ?
|
|
1644
|
+
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;
|
|
1055
1645
|
return { model, promptTokens, completionTokens, outputText };
|
|
1056
1646
|
}
|
|
1057
1647
|
|
|
1058
1648
|
// package.json
|
|
1059
1649
|
var package_default = {
|
|
1060
1650
|
name: "@raindrop-ai/langchain",
|
|
1061
|
-
version: "0.0.
|
|
1651
|
+
version: "0.0.5",
|
|
1062
1652
|
description: "Raindrop integration for LangChain",
|
|
1063
1653
|
main: "dist/index.js",
|
|
1064
1654
|
module: "dist/index.mjs",
|
|
@@ -1132,6 +1722,7 @@ function createRaindropLangChain(opts) {
|
|
|
1132
1722
|
var _a, _b;
|
|
1133
1723
|
const writeKey = opts.writeKey;
|
|
1134
1724
|
const enabled = !!writeKey;
|
|
1725
|
+
setMaxTextFieldChars(opts.maxTextFieldChars);
|
|
1135
1726
|
if (!writeKey) {
|
|
1136
1727
|
console.warn("[raindrop-ai/langchain] writeKey not provided; telemetry shipping is disabled");
|
|
1137
1728
|
}
|
|
@@ -1140,6 +1731,7 @@ function createRaindropLangChain(opts) {
|
|
|
1140
1731
|
endpoint: opts.endpoint,
|
|
1141
1732
|
enabled,
|
|
1142
1733
|
debug: (_a = opts.debug) != null ? _a : false,
|
|
1734
|
+
projectId: opts.projectId,
|
|
1143
1735
|
sdkName: "langchain",
|
|
1144
1736
|
libraryName,
|
|
1145
1737
|
libraryVersion
|
|
@@ -1149,6 +1741,7 @@ function createRaindropLangChain(opts) {
|
|
|
1149
1741
|
endpoint: opts.endpoint,
|
|
1150
1742
|
enabled,
|
|
1151
1743
|
debug: (_b = opts.debug) != null ? _b : false,
|
|
1744
|
+
projectId: opts.projectId,
|
|
1152
1745
|
sdkName: "langchain",
|
|
1153
1746
|
serviceName: "raindrop.langchain",
|
|
1154
1747
|
serviceVersion: libraryVersion
|