@raindrop-ai/opencode-plugin 0.1.3 → 0.2.0
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 +27 -2
- package/dist/chunk-4DAI6CYK.js +2573 -0
- package/dist/index.cjs +1147 -253
- package/dist/index.d-vgBcEZbX.d.cts +89 -0
- package/dist/index.d-vgBcEZbX.d.ts +89 -0
- package/dist/index.d.cts +6 -69
- package/dist/index.d.ts +6 -69
- package/dist/index.js +47 -1695
- package/dist/v2.cjs +3327 -0
- package/dist/v2.d.cts +41 -0
- package/dist/v2.d.ts +41 -0
- package/dist/v2.js +761 -0
- package/package.json +23 -4
package/dist/index.js
CHANGED
|
@@ -1,1684 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
16
|
-
function randomUUID() {
|
|
17
|
-
const cryptoObj = getCrypto();
|
|
18
|
-
if (cryptoObj && typeof cryptoObj.randomUUID === "function") {
|
|
19
|
-
return cryptoObj.randomUUID();
|
|
20
|
-
}
|
|
21
|
-
const b = randomBytes(16);
|
|
22
|
-
b[6] = b[6] & 15 | 64;
|
|
23
|
-
b[8] = b[8] & 63 | 128;
|
|
24
|
-
const hex = [...b].map((x) => x.toString(16).padStart(2, "0")).join("");
|
|
25
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
26
|
-
}
|
|
27
|
-
function base64Encode(bytes) {
|
|
28
|
-
const maybeBuffer = globalThis.Buffer;
|
|
29
|
-
if (maybeBuffer) {
|
|
30
|
-
return maybeBuffer.from(bytes).toString("base64");
|
|
31
|
-
}
|
|
32
|
-
let binary = "";
|
|
33
|
-
for (let i2 = 0; i2 < bytes.length; i2++) {
|
|
34
|
-
binary += String.fromCharCode(bytes[i2]);
|
|
35
|
-
}
|
|
36
|
-
const btoaFn = globalThis.btoa;
|
|
37
|
-
if (typeof btoaFn === "function") return btoaFn(binary);
|
|
38
|
-
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
39
|
-
let out = "";
|
|
40
|
-
let i = 0;
|
|
41
|
-
while (i < binary.length) {
|
|
42
|
-
const c1 = binary.charCodeAt(i++) & 255;
|
|
43
|
-
const c2 = i < binary.length ? binary.charCodeAt(i++) & 255 : NaN;
|
|
44
|
-
const c3 = i < binary.length ? binary.charCodeAt(i++) & 255 : NaN;
|
|
45
|
-
const e1 = c1 >> 2;
|
|
46
|
-
const e2 = (c1 & 3) << 4 | (Number.isNaN(c2) ? 0 : c2 >> 4);
|
|
47
|
-
const e3 = Number.isNaN(c2) ? 64 : (c2 & 15) << 2 | (Number.isNaN(c3) ? 0 : c3 >> 6);
|
|
48
|
-
const e4 = Number.isNaN(c3) ? 64 : c3 & 63;
|
|
49
|
-
out += alphabet.charAt(e1);
|
|
50
|
-
out += alphabet.charAt(e2);
|
|
51
|
-
out += e3 === 64 ? "=" : alphabet.charAt(e3);
|
|
52
|
-
out += e4 === 64 ? "=" : alphabet.charAt(e4);
|
|
53
|
-
}
|
|
54
|
-
return out;
|
|
55
|
-
}
|
|
56
|
-
function generateId() {
|
|
57
|
-
return randomUUID();
|
|
58
|
-
}
|
|
59
|
-
var HANDOFF_ATTRIBUTE_SUFFIXES = {
|
|
60
|
-
mode: "raindrop.handoff.mode",
|
|
61
|
-
childEventId: "raindrop.handoff.childEventId",
|
|
62
|
-
parentEventId: "raindrop.handoff.parentEventId",
|
|
63
|
-
parentSpanId: "raindrop.handoff.parentSpanId",
|
|
64
|
-
name: "raindrop.handoff.name",
|
|
65
|
-
terminal: "raindrop.handoff.terminal",
|
|
66
|
-
agentRole: "raindrop.agent.role"
|
|
67
|
-
};
|
|
68
|
-
var HANDOFF_TERMINAL_PROPERTY_KEY = HANDOFF_ATTRIBUTE_SUFFIXES.terminal;
|
|
69
|
-
var TRACEPARENT_HEADER = "traceparent";
|
|
70
|
-
var BAGGAGE_HEADER = "baggage";
|
|
71
|
-
var HANDOFF_HEADER = "x-raindrop-handoff";
|
|
72
|
-
var UNRESOLVED_CARRIER_WARNING = `[raindrop] resume: headers were supplied but no hand-off carrier was found on them (searched ${HANDOFF_HEADER}, ${TRACEPARENT_HEADER}, ${BAGGAGE_HEADER}). This run reports as an UNLINKED event, and the launcher that dispatched it will stay on "queued" forever because nothing ever references its child. Passing headers means a parent handed off, so no carrier on them is almost certainly a defect: a gateway or proxy stripping ${HANDOFF_HEADER}, a middleware overwriting ${BAGGAGE_HEADER}, a request forwarded without its headers, or a hand-built carrier whose field names do not match. Send every header the launcher's dispatch returned. Logged once per process; pass no headers for a directly-invoked sub-agent to silence it.`;
|
|
73
|
-
function runWithTracingSuppressed(fn) {
|
|
74
|
-
const hook = globalThis.RAINDROP_SUPPRESS_TRACING;
|
|
75
|
-
if (typeof hook !== "function") return fn();
|
|
76
|
-
let started = false;
|
|
77
|
-
try {
|
|
78
|
-
return hook(() => {
|
|
79
|
-
started = true;
|
|
80
|
-
return fn();
|
|
81
|
-
});
|
|
82
|
-
} catch (err) {
|
|
83
|
-
if (started) throw err;
|
|
84
|
-
return fn();
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
88
|
-
var MAX_RETRY_DELAY_MS = 3e4;
|
|
89
|
-
function wait(ms) {
|
|
90
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
91
|
-
}
|
|
92
|
-
function formatEndpoint(endpoint) {
|
|
93
|
-
if (!endpoint) return void 0;
|
|
94
|
-
return endpoint.endsWith("/") ? endpoint : `${endpoint}/`;
|
|
95
|
-
}
|
|
96
|
-
function redactUrlForLog(url) {
|
|
97
|
-
try {
|
|
98
|
-
const parsed = new URL(url);
|
|
99
|
-
parsed.username = "";
|
|
100
|
-
parsed.password = "";
|
|
101
|
-
parsed.search = "";
|
|
102
|
-
parsed.hash = "";
|
|
103
|
-
return parsed.toString();
|
|
104
|
-
} catch (e) {
|
|
105
|
-
return "<unparseable-url>";
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
var RATE_LIMITED_LOG_INTERVAL_MS = 3e4;
|
|
109
|
-
var rateLimitedLogLast = /* @__PURE__ */ new Map();
|
|
110
|
-
function rateLimitedLog(key, log) {
|
|
111
|
-
const now = Date.now();
|
|
112
|
-
const last = rateLimitedLogLast.get(key);
|
|
113
|
-
if (last !== void 0 && now - last < RATE_LIMITED_LOG_INTERVAL_MS) {
|
|
114
|
-
return false;
|
|
115
|
-
}
|
|
116
|
-
rateLimitedLogLast.set(key, now);
|
|
117
|
-
log();
|
|
118
|
-
return true;
|
|
119
|
-
}
|
|
120
|
-
async function raceWithTimeout(promise, timeoutMs) {
|
|
121
|
-
let timer;
|
|
122
|
-
const settledInTime = await Promise.race([
|
|
123
|
-
promise.then(
|
|
124
|
-
() => true,
|
|
125
|
-
() => true
|
|
126
|
-
),
|
|
127
|
-
new Promise((resolve) => {
|
|
128
|
-
var _a;
|
|
129
|
-
timer = setTimeout(() => resolve(false), Math.max(0, timeoutMs));
|
|
130
|
-
(_a = timer.unref) == null ? void 0 : _a.call(timer);
|
|
131
|
-
})
|
|
132
|
-
]);
|
|
133
|
-
if (timer) clearTimeout(timer);
|
|
134
|
-
return settledInTime;
|
|
135
|
-
}
|
|
136
|
-
function parseRetryAfter(headers) {
|
|
137
|
-
var _a;
|
|
138
|
-
const value = (_a = headers.get("Retry-After")) != null ? _a : headers.get("retry-after");
|
|
139
|
-
if (!value) return void 0;
|
|
140
|
-
const asNumber = Number(value);
|
|
141
|
-
if (value.trim() !== "" && !Number.isNaN(asNumber)) return asNumber * 1e3;
|
|
142
|
-
const asDate = new Date(value).getTime();
|
|
143
|
-
if (!Number.isNaN(asDate)) {
|
|
144
|
-
const delta = asDate - Date.now();
|
|
145
|
-
return delta > 0 ? delta : 0;
|
|
146
|
-
}
|
|
147
|
-
return void 0;
|
|
148
|
-
}
|
|
149
|
-
function getRetryDelayMs(attemptNumber, previousError) {
|
|
150
|
-
if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
|
|
151
|
-
const v = previousError.retryAfterMs;
|
|
152
|
-
if (typeof v === "number") return Math.min(Math.max(0, v), MAX_RETRY_DELAY_MS);
|
|
153
|
-
}
|
|
154
|
-
if (attemptNumber <= 1) return 0;
|
|
155
|
-
const base = 500;
|
|
156
|
-
const factor = Math.pow(2, attemptNumber - 2);
|
|
157
|
-
return Math.min(base * factor, MAX_RETRY_DELAY_MS);
|
|
158
|
-
}
|
|
159
|
-
async function withRetry(operation, opName, opts) {
|
|
160
|
-
const prefix = opts.sdkName ? `[raindrop-ai/${opts.sdkName}]` : "[raindrop-ai/core]";
|
|
161
|
-
let lastError = void 0;
|
|
162
|
-
for (let attemptNumber = 1; attemptNumber <= opts.maxAttempts; attemptNumber++) {
|
|
163
|
-
if (attemptNumber > 1) {
|
|
164
|
-
const delay = getRetryDelayMs(attemptNumber, lastError);
|
|
165
|
-
if (opts.debug) {
|
|
166
|
-
console.warn(
|
|
167
|
-
`${prefix} ${opName} retry ${attemptNumber}/${opts.maxAttempts} in ${delay}ms`
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
if (delay > 0) await wait(delay);
|
|
171
|
-
} else if (opts.debug) {
|
|
172
|
-
console.log(`${prefix} ${opName} attempt ${attemptNumber}/${opts.maxAttempts}`);
|
|
173
|
-
}
|
|
174
|
-
try {
|
|
175
|
-
return await operation();
|
|
176
|
-
} catch (err) {
|
|
177
|
-
lastError = err;
|
|
178
|
-
if (opts.debug) {
|
|
179
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
180
|
-
console.warn(
|
|
181
|
-
`${prefix} ${opName} attempt ${attemptNumber} failed: ${msg}${attemptNumber === opts.maxAttempts ? " (no more retries)" : ""}`
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
|
-
if (lastError && typeof lastError === "object" && "retryable" in lastError && !lastError.retryable)
|
|
185
|
-
break;
|
|
186
|
-
if (attemptNumber === opts.maxAttempts) break;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
190
|
-
}
|
|
191
|
-
async function postJson(url, body, headers, opts) {
|
|
192
|
-
var _a;
|
|
193
|
-
const opName = `POST ${redactUrlForLog(url)}`;
|
|
194
|
-
const timeoutMs = (_a = opts.timeoutMs) != null ? _a : DEFAULT_REQUEST_TIMEOUT_MS;
|
|
195
|
-
await withRetry(
|
|
196
|
-
async () => {
|
|
197
|
-
const resp = await runWithTracingSuppressed(
|
|
198
|
-
() => fetch(url, {
|
|
199
|
-
method: "POST",
|
|
200
|
-
headers: {
|
|
201
|
-
"Content-Type": "application/json",
|
|
202
|
-
...headers
|
|
203
|
-
},
|
|
204
|
-
body: JSON.stringify(body),
|
|
205
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
206
|
-
})
|
|
207
|
-
);
|
|
208
|
-
if (!resp.ok) {
|
|
209
|
-
const text = await resp.text().catch(() => "");
|
|
210
|
-
const err = new Error(
|
|
211
|
-
`HTTP ${resp.status} ${resp.statusText}${text ? `: ${text}` : ""}`
|
|
212
|
-
);
|
|
213
|
-
const retryAfterMs = parseRetryAfter(resp.headers);
|
|
214
|
-
if (typeof retryAfterMs === "number") err.retryAfterMs = retryAfterMs;
|
|
215
|
-
err.retryable = resp.status === 429 || resp.status >= 500;
|
|
216
|
-
throw err;
|
|
217
|
-
}
|
|
218
|
-
},
|
|
219
|
-
opName,
|
|
220
|
-
opts
|
|
221
|
-
);
|
|
222
|
-
}
|
|
223
|
-
var DEFAULT_MAX_TEXT_FIELD_CHARS = 1e6;
|
|
224
|
-
var TRUNCATION_MARKER = "...[truncated by raindrop]";
|
|
225
|
-
var currentDefaultMaxTextFieldChars = DEFAULT_MAX_TEXT_FIELD_CHARS;
|
|
226
|
-
function resolveMaxTextFieldChars(value) {
|
|
227
|
-
if (typeof value === "number" && Number.isFinite(value) && value > 0) {
|
|
228
|
-
return Math.floor(value);
|
|
229
|
-
}
|
|
230
|
-
return currentDefaultMaxTextFieldChars;
|
|
231
|
-
}
|
|
232
|
-
function truncateToLimit(text, limit) {
|
|
233
|
-
if (limit > TRUNCATION_MARKER.length) {
|
|
234
|
-
return text.slice(0, limit - TRUNCATION_MARKER.length) + TRUNCATION_MARKER;
|
|
235
|
-
}
|
|
236
|
-
return text.slice(0, Math.max(0, limit));
|
|
237
|
-
}
|
|
238
|
-
function capText(value, limit) {
|
|
239
|
-
if (typeof value !== "string") return value;
|
|
240
|
-
const max = limit != null ? limit : currentDefaultMaxTextFieldChars;
|
|
241
|
-
if (value.length <= max) return value;
|
|
242
|
-
return truncateToLimit(value, max);
|
|
243
|
-
}
|
|
244
|
-
var SpanStatusCode = {
|
|
245
|
-
UNSET: 0,
|
|
246
|
-
OK: 1,
|
|
247
|
-
ERROR: 2
|
|
248
|
-
};
|
|
249
|
-
function createSpanIds(parent) {
|
|
250
|
-
const traceId = parent ? parent.traceIdB64 : base64Encode(randomBytes(16));
|
|
251
|
-
const spanId = base64Encode(randomBytes(8));
|
|
252
|
-
return {
|
|
253
|
-
traceIdB64: traceId,
|
|
254
|
-
spanIdB64: spanId,
|
|
255
|
-
parentSpanIdB64: parent ? parent.spanIdB64 : void 0
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
function nowUnixNanoString() {
|
|
259
|
-
return Date.now().toString() + "000000";
|
|
260
|
-
}
|
|
261
|
-
function attrString(key, value) {
|
|
262
|
-
if (value === void 0) return void 0;
|
|
263
|
-
return { key, value: { stringValue: value } };
|
|
264
|
-
}
|
|
265
|
-
function attrInt(key, value) {
|
|
266
|
-
if (value === void 0) return void 0;
|
|
267
|
-
if (!Number.isFinite(value)) return void 0;
|
|
268
|
-
return { key, value: { intValue: String(Math.trunc(value)) } };
|
|
269
|
-
}
|
|
270
|
-
function attrDouble(key, value) {
|
|
271
|
-
if (value === void 0) return void 0;
|
|
272
|
-
if (!Number.isFinite(value)) return void 0;
|
|
273
|
-
return { key, value: { doubleValue: value } };
|
|
274
|
-
}
|
|
275
|
-
function buildOtlpSpan(args) {
|
|
276
|
-
const attrs = args.attributes.filter((x) => x !== void 0);
|
|
277
|
-
const span = {
|
|
278
|
-
traceId: args.ids.traceIdB64,
|
|
279
|
-
spanId: args.ids.spanIdB64,
|
|
280
|
-
name: args.name,
|
|
281
|
-
startTimeUnixNano: args.startTimeUnixNano,
|
|
282
|
-
endTimeUnixNano: args.endTimeUnixNano
|
|
283
|
-
};
|
|
284
|
-
if (args.ids.parentSpanIdB64) span.parentSpanId = args.ids.parentSpanIdB64;
|
|
285
|
-
if (attrs.length) span.attributes = attrs;
|
|
286
|
-
if (args.status) span.status = args.status;
|
|
287
|
-
return span;
|
|
288
|
-
}
|
|
289
|
-
function buildExportTraceServiceRequest(spans, serviceName = "raindrop.core", serviceVersion = "0.0.0") {
|
|
290
|
-
return {
|
|
291
|
-
resourceSpans: [
|
|
292
|
-
{
|
|
293
|
-
resource: {
|
|
294
|
-
attributes: [{ key: "service.name", value: { stringValue: serviceName } }]
|
|
295
|
-
},
|
|
296
|
-
scopeSpans: [
|
|
297
|
-
{
|
|
298
|
-
scope: { name: serviceName, version: serviceVersion },
|
|
299
|
-
spans
|
|
300
|
-
}
|
|
301
|
-
]
|
|
302
|
-
}
|
|
303
|
-
]
|
|
304
|
-
};
|
|
305
|
-
}
|
|
306
|
-
var LOCAL_DEBUGGER_ENV_VAR = "RAINDROP_LOCAL_DEBUGGER";
|
|
307
|
-
var WORKSHOP_ENV_VAR = "RAINDROP_WORKSHOP";
|
|
308
|
-
var DEFAULT_LOCAL_WORKSHOP_URL = "http://localhost:5899/v1/";
|
|
309
|
-
function readEnvVar(name) {
|
|
310
|
-
var _a;
|
|
311
|
-
try {
|
|
312
|
-
const env = (_a = globalThis == null ? void 0 : globalThis.process) == null ? void 0 : _a.env;
|
|
313
|
-
if (env && typeof env[name] === "string" && env[name].length > 0) {
|
|
314
|
-
return env[name];
|
|
315
|
-
}
|
|
316
|
-
} catch (e) {
|
|
317
|
-
}
|
|
318
|
-
return void 0;
|
|
319
|
-
}
|
|
320
|
-
function readWorkshopEnv() {
|
|
321
|
-
const raw = readEnvVar(WORKSHOP_ENV_VAR);
|
|
322
|
-
if (raw === void 0) return void 0;
|
|
323
|
-
const trimmed = raw.trim();
|
|
324
|
-
if (trimmed.length === 0) return void 0;
|
|
325
|
-
if (/^https?:\/\//i.test(trimmed)) return { url: trimmed };
|
|
326
|
-
if (/^(1|true|yes|on)$/i.test(trimmed)) return "enable";
|
|
327
|
-
if (/^(0|false|no|off)$/i.test(trimmed)) return "disable";
|
|
328
|
-
return void 0;
|
|
329
|
-
}
|
|
330
|
-
function isLocalDevHost(hostname) {
|
|
331
|
-
if (!hostname) return false;
|
|
332
|
-
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "0.0.0.0" || hostname === "::1") {
|
|
333
|
-
return true;
|
|
334
|
-
}
|
|
335
|
-
if (hostname.endsWith(".localhost")) return true;
|
|
336
|
-
return false;
|
|
337
|
-
}
|
|
338
|
-
function readRuntimeHostname() {
|
|
339
|
-
try {
|
|
340
|
-
const loc = globalThis == null ? void 0 : globalThis.location;
|
|
341
|
-
if (loc && typeof loc.hostname === "string" && loc.hostname.length > 0) {
|
|
342
|
-
return loc.hostname;
|
|
343
|
-
}
|
|
344
|
-
} catch (e) {
|
|
345
|
-
}
|
|
346
|
-
return void 0;
|
|
347
|
-
}
|
|
348
|
-
function shouldAutoEnableLocalWorkshop() {
|
|
349
|
-
if (isLocalDevHost(readRuntimeHostname())) return true;
|
|
350
|
-
if (readEnvVar("NODE_ENV") === "development") return true;
|
|
351
|
-
return false;
|
|
352
|
-
}
|
|
353
|
-
function resolveLocalDebuggerBaseUrl(baseUrl) {
|
|
354
|
-
var _a, _b, _c;
|
|
355
|
-
if (baseUrl === null) return null;
|
|
356
|
-
if (typeof baseUrl === "string" && baseUrl.length > 0) {
|
|
357
|
-
return (_a = formatEndpoint(baseUrl)) != null ? _a : null;
|
|
358
|
-
}
|
|
359
|
-
const explicitUrlEnv = readEnvVar(LOCAL_DEBUGGER_ENV_VAR);
|
|
360
|
-
if (explicitUrlEnv) return (_b = formatEndpoint(explicitUrlEnv)) != null ? _b : null;
|
|
361
|
-
const workshopEnv = readWorkshopEnv();
|
|
362
|
-
if (workshopEnv === "disable") return null;
|
|
363
|
-
if (workshopEnv === "enable") return DEFAULT_LOCAL_WORKSHOP_URL;
|
|
364
|
-
if (workshopEnv && "url" in workshopEnv) return (_c = formatEndpoint(workshopEnv.url)) != null ? _c : null;
|
|
365
|
-
if (shouldAutoEnableLocalWorkshop()) return DEFAULT_LOCAL_WORKSHOP_URL;
|
|
366
|
-
return null;
|
|
367
|
-
}
|
|
368
|
-
function mirrorTraceExportToLocalDebugger(body, options = {}) {
|
|
369
|
-
var _a;
|
|
370
|
-
const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
|
|
371
|
-
if (!baseUrl) return;
|
|
372
|
-
void postJson(`${baseUrl}traces`, body, {}, {
|
|
373
|
-
maxAttempts: 1,
|
|
374
|
-
debug: (_a = options.debug) != null ? _a : false,
|
|
375
|
-
sdkName: options.sdkName
|
|
376
|
-
}).catch(() => {
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
function mirrorPartialEventToLocalDebugger(event, options = {}) {
|
|
380
|
-
var _a;
|
|
381
|
-
const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
|
|
382
|
-
if (!baseUrl) return;
|
|
383
|
-
const headers = options.writeKey ? { Authorization: `Bearer ${options.writeKey}` } : {};
|
|
384
|
-
void postJson(`${baseUrl}events/track_partial`, event, headers, {
|
|
385
|
-
maxAttempts: 1,
|
|
386
|
-
debug: (_a = options.debug) != null ? _a : false,
|
|
387
|
-
sdkName: options.sdkName
|
|
388
|
-
}).catch(() => {
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
var PROJECT_ID_HEADER = "X-Raindrop-Project-Id";
|
|
392
|
-
var PROJECT_ID_SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
393
|
-
function isValidProjectIdSlug(value) {
|
|
394
|
-
return PROJECT_ID_SLUG_PATTERN.test(value);
|
|
395
|
-
}
|
|
396
|
-
function normalizeProjectId(raw, opts) {
|
|
397
|
-
if (typeof raw !== "string") return void 0;
|
|
398
|
-
const trimmed = raw.trim();
|
|
399
|
-
if (!trimmed) return void 0;
|
|
400
|
-
if (!isValidProjectIdSlug(trimmed) && opts.debug) {
|
|
401
|
-
console.warn(
|
|
402
|
-
`${opts.prefix} projectId "${trimmed}" does not match slug ${PROJECT_ID_SLUG_PATTERN.source}; sending anyway \u2014 backend may reject with HTTP 400`
|
|
403
|
-
);
|
|
404
|
-
}
|
|
405
|
-
return trimmed;
|
|
406
|
-
}
|
|
407
|
-
function projectIdHeaders(projectId) {
|
|
408
|
-
return projectId ? { [PROJECT_ID_HEADER]: projectId } : {};
|
|
409
|
-
}
|
|
410
|
-
var SHUTDOWN_DEADLINE_MS = 1e4;
|
|
411
|
-
var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
412
|
-
function mergePatches(target, source) {
|
|
413
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
414
|
-
const out = { ...target, ...source };
|
|
415
|
-
if (target.properties || source.properties) {
|
|
416
|
-
out.properties = { ...(_a = target.properties) != null ? _a : {}, ...(_b = source.properties) != null ? _b : {} };
|
|
417
|
-
}
|
|
418
|
-
if (target.usage || source.usage) {
|
|
419
|
-
out.usage = { ...(_c = target.usage) != null ? _c : {} };
|
|
420
|
-
if (((_d = source.usage) == null ? void 0 : _d.promptTokens) !== void 0) {
|
|
421
|
-
out.usage.promptTokens = source.usage.promptTokens;
|
|
422
|
-
}
|
|
423
|
-
if (((_e = source.usage) == null ? void 0 : _e.completionTokens) !== void 0) {
|
|
424
|
-
out.usage.completionTokens = source.usage.completionTokens;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
if (source.error === void 0) {
|
|
428
|
-
out.error = target.error;
|
|
429
|
-
}
|
|
430
|
-
if (target.featureFlags || source.featureFlags) {
|
|
431
|
-
out.featureFlags = { ...(_f = target.featureFlags) != null ? _f : {}, ...(_g = source.featureFlags) != null ? _g : {} };
|
|
432
|
-
}
|
|
433
|
-
if (target.attachments || source.attachments) {
|
|
434
|
-
out.attachments = [...(_h = target.attachments) != null ? _h : [], ...(_i = source.attachments) != null ? _i : []];
|
|
435
|
-
}
|
|
436
|
-
return out;
|
|
437
|
-
}
|
|
438
|
-
function isErrorLike(value) {
|
|
439
|
-
return value !== null && typeof value === "object";
|
|
440
|
-
}
|
|
441
|
-
function eventTelemetryProperties(patch) {
|
|
442
|
-
var _a, _b;
|
|
443
|
-
const properties = {};
|
|
444
|
-
if (((_a = patch.usage) == null ? void 0 : _a.promptTokens) !== void 0 && Number.isFinite(patch.usage.promptTokens)) {
|
|
445
|
-
properties["ai.usage.prompt_tokens"] = patch.usage.promptTokens;
|
|
446
|
-
}
|
|
447
|
-
if (((_b = patch.usage) == null ? void 0 : _b.completionTokens) !== void 0 && Number.isFinite(patch.usage.completionTokens)) {
|
|
448
|
-
properties["ai.usage.completion_tokens"] = patch.usage.completionTokens;
|
|
449
|
-
}
|
|
450
|
-
if (patch.error !== void 0 && patch.error !== null) {
|
|
451
|
-
const constructorName = patch.error instanceof Error && patch.error.constructor !== Error ? patch.error.constructor.name : void 0;
|
|
452
|
-
const errorType = patch.error instanceof Error ? patch.error.name && patch.error.name !== "Error" ? patch.error.name : constructorName || patch.error.name || "Error" : isErrorLike(patch.error) && typeof patch.error.name === "string" && patch.error.name.length > 0 ? patch.error.name : "Error";
|
|
453
|
-
const errorMessage = patch.error instanceof Error ? patch.error.message : isErrorLike(patch.error) && typeof patch.error.message === "string" ? patch.error.message : String(patch.error);
|
|
454
|
-
properties["error.type"] = errorType;
|
|
455
|
-
properties["error.message"] = errorMessage;
|
|
456
|
-
}
|
|
457
|
-
return properties;
|
|
458
|
-
}
|
|
459
|
-
var EventShipper = class {
|
|
460
|
-
constructor(opts) {
|
|
461
|
-
this.buffers = /* @__PURE__ */ new Map();
|
|
462
|
-
this.sticky = /* @__PURE__ */ new Map();
|
|
463
|
-
this.timers = /* @__PURE__ */ new Map();
|
|
464
|
-
this.inFlight = /* @__PURE__ */ new Set();
|
|
465
|
-
this.hasShutdown = false;
|
|
466
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
467
|
-
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
468
|
-
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
469
|
-
this.enabled = opts.enabled !== false;
|
|
470
|
-
this.debug = opts.debug;
|
|
471
|
-
this.partialFlushMs = (_c = opts.partialFlushMs) != null ? _c : 1e3;
|
|
472
|
-
this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
|
|
473
|
-
this.prefix = `[raindrop-ai/${this.sdkName}]`;
|
|
474
|
-
this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
|
|
475
|
-
this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
|
|
476
|
-
this.localDebuggerUrl = (_f = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _f : void 0;
|
|
477
|
-
if (this.debug && this.localDebuggerUrl) {
|
|
478
|
-
console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
|
|
479
|
-
}
|
|
480
|
-
this.projectId = normalizeProjectId(opts.projectId, {
|
|
481
|
-
debug: this.debug,
|
|
482
|
-
prefix: this.prefix
|
|
483
|
-
});
|
|
484
|
-
const isNode = typeof process !== "undefined" && typeof process.version === "string";
|
|
485
|
-
this.context = {
|
|
486
|
-
library: {
|
|
487
|
-
name: (_g = opts.libraryName) != null ? _g : "@raindrop-ai/core",
|
|
488
|
-
version: (_h = opts.libraryVersion) != null ? _h : "0.0.0"
|
|
489
|
-
},
|
|
490
|
-
metadata: {
|
|
491
|
-
jsRuntime: isNode ? "node" : "web",
|
|
492
|
-
...isNode ? { nodeVersion: process.version } : {}
|
|
493
|
-
}
|
|
494
|
-
};
|
|
495
|
-
}
|
|
496
|
-
isDebugEnabled() {
|
|
497
|
-
return this.debug;
|
|
498
|
-
}
|
|
499
|
-
authHeaders() {
|
|
500
|
-
return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
|
|
501
|
-
}
|
|
502
|
-
requestHeaders() {
|
|
503
|
-
return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
|
|
504
|
-
}
|
|
505
|
-
/**
|
|
506
|
-
* Build the retry/timeout options for one POST, honoring the shutdown
|
|
507
|
-
* deadline. Returns `null` when the shutdown drain window is exhausted —
|
|
508
|
-
* the caller must drop the payload (with a rate-limited warning) instead
|
|
509
|
-
* of issuing a request that could outlive process exit.
|
|
510
|
-
*
|
|
511
|
-
* Checked fresh on EVERY send, so a shutdown that begins while the flush
|
|
512
|
-
* path is mid-drain takes effect immediately: no further retries, and the
|
|
513
|
-
* per-attempt timeout is clamped to the remaining window. After
|
|
514
|
-
* `shutdown()` returns (deadline cleared, `hasShutdown` still set),
|
|
515
|
-
* sends — late callers, or flush work the deadline abandoned mid-drain —
|
|
516
|
-
* run as a single short attempt rather than regaining the full retry
|
|
517
|
-
* schedule.
|
|
518
|
-
*/
|
|
519
|
-
requestOpts() {
|
|
520
|
-
if (this.shutdownDeadlineAt !== void 0) {
|
|
521
|
-
const remainingMs = this.shutdownDeadlineAt - Date.now();
|
|
522
|
-
if (remainingMs <= 0) return null;
|
|
523
|
-
return {
|
|
524
|
-
maxAttempts: 1,
|
|
525
|
-
debug: this.debug,
|
|
526
|
-
sdkName: this.sdkName,
|
|
527
|
-
timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
|
|
528
|
-
};
|
|
529
|
-
}
|
|
530
|
-
if (this.hasShutdown) {
|
|
531
|
-
return {
|
|
532
|
-
maxAttempts: 1,
|
|
533
|
-
debug: this.debug,
|
|
534
|
-
sdkName: this.sdkName,
|
|
535
|
-
timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
|
|
536
|
-
};
|
|
537
|
-
}
|
|
538
|
-
return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
|
|
539
|
-
}
|
|
540
|
-
async patch(eventId, patch) {
|
|
541
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
542
|
-
if (!this.enabled) return;
|
|
543
|
-
if (!eventId || !eventId.trim()) return;
|
|
544
|
-
const maxChars = resolveMaxTextFieldChars(this.maxTextFieldCharsOpt);
|
|
545
|
-
if (typeof patch.input === "string" && patch.input.length > maxChars) {
|
|
546
|
-
patch = { ...patch, input: capText(patch.input, maxChars) };
|
|
547
|
-
}
|
|
548
|
-
if (typeof patch.output === "string" && patch.output.length > maxChars) {
|
|
549
|
-
patch = { ...patch, output: capText(patch.output, maxChars) };
|
|
550
|
-
}
|
|
551
|
-
if (this.debug) {
|
|
552
|
-
console.log(`${this.prefix} queue patch`, {
|
|
553
|
-
eventId,
|
|
554
|
-
userId: patch.userId,
|
|
555
|
-
convoId: patch.convoId,
|
|
556
|
-
eventName: patch.eventName,
|
|
557
|
-
hasInput: typeof patch.input === "string" && patch.input.length > 0,
|
|
558
|
-
hasOutput: typeof patch.output === "string" && patch.output.length > 0,
|
|
559
|
-
attachments: (_b = (_a = patch.attachments) == null ? void 0 : _a.length) != null ? _b : 0,
|
|
560
|
-
isPending: patch.isPending
|
|
561
|
-
});
|
|
562
|
-
}
|
|
563
|
-
const sticky = (_c = this.sticky.get(eventId)) != null ? _c : {};
|
|
564
|
-
const existing = (_d = this.buffers.get(eventId)) != null ? _d : {};
|
|
565
|
-
const merged = mergePatches(existing, patch);
|
|
566
|
-
merged.isPending = (_g = (_f = (_e = patch.isPending) != null ? _e : existing.isPending) != null ? _f : sticky.isPending) != null ? _g : true;
|
|
567
|
-
this.buffers.set(eventId, merged);
|
|
568
|
-
this.sticky.set(eventId, {
|
|
569
|
-
userId: (_h = merged.userId) != null ? _h : sticky.userId,
|
|
570
|
-
convoId: (_i = merged.convoId) != null ? _i : sticky.convoId,
|
|
571
|
-
eventName: (_j = merged.eventName) != null ? _j : sticky.eventName,
|
|
572
|
-
isPending: (_k = merged.isPending) != null ? _k : sticky.isPending
|
|
573
|
-
});
|
|
574
|
-
const t = this.timers.get(eventId);
|
|
575
|
-
if (t) clearTimeout(t);
|
|
576
|
-
if (merged.isPending === false) {
|
|
577
|
-
await this.flushOne(eventId);
|
|
578
|
-
return;
|
|
579
|
-
}
|
|
580
|
-
const timeout = setTimeout(() => {
|
|
581
|
-
void this.flushOne(eventId).catch(() => {
|
|
582
|
-
});
|
|
583
|
-
}, this.partialFlushMs);
|
|
584
|
-
this.timers.set(eventId, timeout);
|
|
585
|
-
}
|
|
586
|
-
async finish(eventId, patch) {
|
|
587
|
-
await this.patch(eventId, { ...patch, isPending: false });
|
|
588
|
-
}
|
|
589
|
-
async flush() {
|
|
590
|
-
if (!this.enabled) return;
|
|
591
|
-
const ids = [...this.buffers.keys()];
|
|
592
|
-
await Promise.all(ids.map((id) => this.flushOne(id)));
|
|
593
|
-
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
594
|
-
})));
|
|
595
|
-
}
|
|
596
|
-
async shutdown() {
|
|
597
|
-
this.hasShutdown = true;
|
|
598
|
-
this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
|
|
599
|
-
try {
|
|
600
|
-
for (const t of this.timers.values()) clearTimeout(t);
|
|
601
|
-
this.timers.clear();
|
|
602
|
-
const settled = await raceWithTimeout(this.flush(), SHUTDOWN_DEADLINE_MS);
|
|
603
|
-
if (!settled) this.warnShutdownDrop("in-flight request(s) at shutdown");
|
|
604
|
-
} finally {
|
|
605
|
-
this.shutdownDeadlineAt = void 0;
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
async trackSignal(signal) {
|
|
609
|
-
var _a, _b;
|
|
610
|
-
if (!this.enabled) return;
|
|
611
|
-
const body = [
|
|
612
|
-
{
|
|
613
|
-
event_id: signal.eventId,
|
|
614
|
-
signal_name: signal.name,
|
|
615
|
-
signal_type: (_a = signal.type) != null ? _a : "default",
|
|
616
|
-
timestamp: signal.timestamp,
|
|
617
|
-
sentiment: signal.sentiment,
|
|
618
|
-
attachment_id: signal.attachmentId,
|
|
619
|
-
properties: {
|
|
620
|
-
...(_b = signal.properties) != null ? _b : {},
|
|
621
|
-
...signal.comment ? { comment: signal.comment } : {},
|
|
622
|
-
...signal.after ? { after: signal.after } : {}
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
];
|
|
626
|
-
if (!this.writeKey) return;
|
|
627
|
-
const url = `${this.baseUrl}signals/track`;
|
|
628
|
-
const opts = this.requestOpts();
|
|
629
|
-
if (!opts) {
|
|
630
|
-
this.warnShutdownDrop("signal");
|
|
631
|
-
return;
|
|
632
|
-
}
|
|
633
|
-
try {
|
|
634
|
-
await postJson(url, body, this.requestHeaders(), opts);
|
|
635
|
-
} catch (err) {
|
|
636
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
637
|
-
rateLimitedLog(
|
|
638
|
-
`${this.prefix}.send_signal_failed`,
|
|
639
|
-
() => console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`)
|
|
640
|
-
);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
async identify(users) {
|
|
644
|
-
if (!this.enabled) return;
|
|
645
|
-
const list = Array.isArray(users) ? users : [users];
|
|
646
|
-
const body = list.filter((user) => {
|
|
647
|
-
if (!(user == null ? void 0 : user.userId) || !user.userId.trim()) {
|
|
648
|
-
if (this.debug) {
|
|
649
|
-
console.warn(`${this.prefix} skipping identify: missing userId`);
|
|
650
|
-
}
|
|
651
|
-
return false;
|
|
652
|
-
}
|
|
653
|
-
return true;
|
|
654
|
-
}).map((user) => {
|
|
655
|
-
var _a;
|
|
656
|
-
return {
|
|
657
|
-
user_id: user.userId,
|
|
658
|
-
traits: (_a = user.traits) != null ? _a : {}
|
|
659
|
-
};
|
|
660
|
-
});
|
|
661
|
-
if (!this.writeKey) return;
|
|
662
|
-
if (body.length === 0) return;
|
|
663
|
-
const url = `${this.baseUrl}users/identify`;
|
|
664
|
-
const opts = this.requestOpts();
|
|
665
|
-
if (!opts) {
|
|
666
|
-
this.warnShutdownDrop("identify");
|
|
667
|
-
return;
|
|
668
|
-
}
|
|
669
|
-
try {
|
|
670
|
-
await postJson(url, body, this.requestHeaders(), opts);
|
|
671
|
-
} catch (err) {
|
|
672
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
673
|
-
rateLimitedLog(
|
|
674
|
-
`${this.prefix}.send_identify_failed`,
|
|
675
|
-
() => console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`)
|
|
676
|
-
);
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
warnShutdownDrop(what) {
|
|
680
|
-
rateLimitedLog(
|
|
681
|
-
`${this.prefix}.shutdown_deadline`,
|
|
682
|
-
() => console.warn(`${this.prefix} shutdown flush deadline exceeded; dropping ${what}`)
|
|
683
|
-
);
|
|
684
|
-
}
|
|
685
|
-
async flushOne(eventId) {
|
|
686
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
687
|
-
if (!this.enabled) return;
|
|
688
|
-
const timer = this.timers.get(eventId);
|
|
689
|
-
if (timer) {
|
|
690
|
-
clearTimeout(timer);
|
|
691
|
-
this.timers.delete(eventId);
|
|
692
|
-
}
|
|
693
|
-
const accumulated = this.buffers.get(eventId);
|
|
694
|
-
this.buffers.delete(eventId);
|
|
695
|
-
if (!accumulated) return;
|
|
696
|
-
const sticky = (_a = this.sticky.get(eventId)) != null ? _a : {};
|
|
697
|
-
const eventName = (_c = (_b = accumulated.eventName) != null ? _b : sticky.eventName) != null ? _c : this.defaultEventName;
|
|
698
|
-
const userId = (_d = accumulated.userId) != null ? _d : sticky.userId;
|
|
699
|
-
if (!userId) {
|
|
700
|
-
if (this.debug) {
|
|
701
|
-
console.warn(`${this.prefix} skipping track_partial for ${eventId}: missing userId`);
|
|
702
|
-
}
|
|
703
|
-
this.sticky.delete(eventId);
|
|
704
|
-
return;
|
|
705
|
-
}
|
|
706
|
-
const { wizardSession, ...restProperties } = (_e = accumulated.properties) != null ? _e : {};
|
|
707
|
-
let telemetryProperties = {};
|
|
708
|
-
try {
|
|
709
|
-
telemetryProperties = eventTelemetryProperties(accumulated);
|
|
710
|
-
} catch (e) {
|
|
711
|
-
}
|
|
712
|
-
const convoId = (_f = accumulated.convoId) != null ? _f : sticky.convoId;
|
|
713
|
-
const isPending = (_h = (_g = accumulated.isPending) != null ? _g : sticky.isPending) != null ? _h : true;
|
|
714
|
-
const payload = {
|
|
715
|
-
event_id: eventId,
|
|
716
|
-
user_id: userId,
|
|
717
|
-
event: eventName,
|
|
718
|
-
timestamp: (_i = accumulated.timestamp) != null ? _i : (/* @__PURE__ */ new Date()).toISOString(),
|
|
719
|
-
ai_data: {
|
|
720
|
-
input: accumulated.input,
|
|
721
|
-
output: accumulated.error !== void 0 && accumulated.error !== null ? void 0 : accumulated.output,
|
|
722
|
-
model: accumulated.model,
|
|
723
|
-
convo_id: convoId
|
|
724
|
-
},
|
|
725
|
-
properties: {
|
|
726
|
-
...restProperties,
|
|
727
|
-
...telemetryProperties,
|
|
728
|
-
...wizardSession ? { "raindrop.wizardSession": wizardSession } : {},
|
|
729
|
-
$context: this.context
|
|
730
|
-
},
|
|
731
|
-
...accumulated.featureFlags && Object.keys(accumulated.featureFlags).length > 0 ? { feature_flags: accumulated.featureFlags } : {},
|
|
732
|
-
attachments: accumulated.attachments,
|
|
733
|
-
is_pending: isPending
|
|
734
|
-
};
|
|
735
|
-
const url = `${this.baseUrl}events/track_partial`;
|
|
736
|
-
if (this.debug) {
|
|
737
|
-
console.log(`${this.prefix} sending track_partial`, {
|
|
738
|
-
eventId,
|
|
739
|
-
eventName,
|
|
740
|
-
userId,
|
|
741
|
-
convoId,
|
|
742
|
-
isPending,
|
|
743
|
-
inputPreview: typeof accumulated.input === "string" ? accumulated.input.slice(0, 120) : void 0,
|
|
744
|
-
outputPreview: typeof accumulated.output === "string" ? accumulated.output.slice(0, 120) : void 0,
|
|
745
|
-
attachments: (_k = (_j = accumulated.attachments) == null ? void 0 : _j.length) != null ? _k : 0,
|
|
746
|
-
attachmentKinds: (_m = (_l = accumulated.attachments) == null ? void 0 : _l.map((a) => ({
|
|
747
|
-
type: a.type,
|
|
748
|
-
role: a.role,
|
|
749
|
-
name: a.name,
|
|
750
|
-
valuePreview: a.value.slice(0, 60)
|
|
751
|
-
}))) != null ? _m : [],
|
|
752
|
-
endpoint: url
|
|
753
|
-
});
|
|
754
|
-
}
|
|
755
|
-
if (this.localDebuggerUrl) {
|
|
756
|
-
mirrorPartialEventToLocalDebugger(payload, {
|
|
757
|
-
baseUrl: this.localDebuggerUrl,
|
|
758
|
-
writeKey: this.writeKey,
|
|
759
|
-
debug: this.debug,
|
|
760
|
-
sdkName: this.sdkName
|
|
761
|
-
});
|
|
762
|
-
}
|
|
763
|
-
if (!this.writeKey) {
|
|
764
|
-
if (!isPending) this.sticky.delete(eventId);
|
|
765
|
-
return;
|
|
766
|
-
}
|
|
767
|
-
const opts = this.requestOpts();
|
|
768
|
-
if (!opts) {
|
|
769
|
-
this.warnShutdownDrop(`track_partial ${eventId}`);
|
|
770
|
-
if (!isPending) this.sticky.delete(eventId);
|
|
771
|
-
return;
|
|
772
|
-
}
|
|
773
|
-
const p = postJson(url, payload, this.requestHeaders(), opts);
|
|
774
|
-
this.inFlight.add(p);
|
|
775
|
-
try {
|
|
776
|
-
try {
|
|
777
|
-
await p;
|
|
778
|
-
if (this.debug) {
|
|
779
|
-
console.log(`${this.prefix} sent track_partial ${eventId} (${eventName})`);
|
|
780
|
-
}
|
|
781
|
-
} catch (err) {
|
|
782
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
783
|
-
rateLimitedLog(
|
|
784
|
-
`${this.prefix}.send_track_partial_failed`,
|
|
785
|
-
() => console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`)
|
|
786
|
-
);
|
|
787
|
-
}
|
|
788
|
-
} finally {
|
|
789
|
-
this.inFlight.delete(p);
|
|
790
|
-
}
|
|
791
|
-
if (!isPending) {
|
|
792
|
-
this.sticky.delete(eventId);
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
};
|
|
796
|
-
var MODEL_USAGE_ATTRIBUTES = {
|
|
797
|
-
usageSource: "raindrop.usage.source",
|
|
798
|
-
providerName: "gen_ai.provider.name",
|
|
799
|
-
requestModel: "gen_ai.request.model",
|
|
800
|
-
responseModel: "gen_ai.response.model",
|
|
801
|
-
inputTokens: "gen_ai.usage.input_tokens",
|
|
802
|
-
outputTokens: "gen_ai.usage.output_tokens",
|
|
803
|
-
reasoningTokens: "gen_ai.usage.reasoning_tokens",
|
|
804
|
-
cacheReadInputTokens: "gen_ai.usage.cache_read_input_tokens",
|
|
805
|
-
cacheWriteInputTokens: "gen_ai.usage.cache_write_input_tokens",
|
|
806
|
-
providerReportedCost: "gen_ai.usage.provider_reported_cost",
|
|
807
|
-
nonCachedInputTokens: "raindrop.usage.input_tokens.non_cached",
|
|
808
|
-
nonReasoningOutputTokens: "raindrop.usage.output_tokens.non_reasoning",
|
|
809
|
-
totalOutputTokens: "raindrop.usage.output_tokens.total",
|
|
810
|
-
reasoningOutputTokens: "raindrop.usage.output_tokens.reasoning",
|
|
811
|
-
cacheReadTokens: "raindrop.usage.input_tokens.cache_read",
|
|
812
|
-
cacheWriteTokens: "raindrop.usage.input_tokens.cache_write"
|
|
813
|
-
};
|
|
814
|
-
var MODEL_PROVIDER_NAMES = [
|
|
815
|
-
["google.vertex.anthropic", "google-vertex"],
|
|
816
|
-
["google.vertex_ai", "google-vertex"],
|
|
817
|
-
["google.vertex", "google-vertex"],
|
|
818
|
-
["gcp.vertex_ai", "google-vertex"],
|
|
819
|
-
["gcp.gemini", "google"],
|
|
820
|
-
["google_vertexai", "google-vertex"],
|
|
821
|
-
["vertex.anthropic", "google-vertex"],
|
|
822
|
-
["amazon-bedrock", "amazon-bedrock"],
|
|
823
|
-
["aws.bedrock", "amazon-bedrock"],
|
|
824
|
-
["bedrock", "amazon-bedrock"],
|
|
825
|
-
["anthropic", "anthropic"],
|
|
826
|
-
["openai", "openai"],
|
|
827
|
-
["google", "google"],
|
|
828
|
-
["az.ai.openai", "azure"],
|
|
829
|
-
["azure", "azure"],
|
|
830
|
-
["openrouter", "openrouter"]
|
|
831
|
-
];
|
|
832
|
-
function canonicalModelProvider(provider) {
|
|
833
|
-
var _a;
|
|
834
|
-
const normalizedProvider = provider.trim().toLowerCase();
|
|
835
|
-
if (normalizedProvider === "gateway") return "vercel";
|
|
836
|
-
const match = MODEL_PROVIDER_NAMES.find(
|
|
837
|
-
([family]) => normalizedProvider === family || normalizedProvider.startsWith(`${family}.`)
|
|
838
|
-
);
|
|
839
|
-
return (_a = match == null ? void 0 : match[1]) != null ? _a : normalizedProvider;
|
|
840
|
-
}
|
|
841
|
-
function tokenCount(value) {
|
|
842
|
-
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
|
|
843
|
-
}
|
|
844
|
-
function buildRawModelUsageAttributes(usage) {
|
|
845
|
-
const providerReportedCost = typeof usage.providerReportedCost === "number" && Number.isFinite(usage.providerReportedCost) && usage.providerReportedCost >= 0 ? usage.providerReportedCost : void 0;
|
|
846
|
-
return [
|
|
847
|
-
attrInt(MODEL_USAGE_ATTRIBUTES.inputTokens, tokenCount(usage.inputTokens)),
|
|
848
|
-
attrInt(MODEL_USAGE_ATTRIBUTES.outputTokens, tokenCount(usage.outputTokens)),
|
|
849
|
-
attrInt(MODEL_USAGE_ATTRIBUTES.reasoningTokens, tokenCount(usage.reasoningTokens)),
|
|
850
|
-
attrInt(MODEL_USAGE_ATTRIBUTES.cacheReadInputTokens, tokenCount(usage.cacheReadInputTokens)),
|
|
851
|
-
attrInt(MODEL_USAGE_ATTRIBUTES.cacheWriteInputTokens, tokenCount(usage.cacheWriteInputTokens)),
|
|
852
|
-
attrDouble(MODEL_USAGE_ATTRIBUTES.providerReportedCost, providerReportedCost)
|
|
853
|
-
].filter((attribute) => attribute !== void 0);
|
|
854
|
-
}
|
|
855
|
-
var STRING_ALIASES = {
|
|
856
|
-
[MODEL_USAGE_ATTRIBUTES.requestModel]: ["ai.model.id", "ai.model"]
|
|
857
|
-
};
|
|
858
|
-
var NUMBER_ALIASES = {
|
|
859
|
-
[MODEL_USAGE_ATTRIBUTES.inputTokens]: [
|
|
860
|
-
"gen_ai.usage.prompt_tokens",
|
|
861
|
-
"ai.usage.prompt_tokens",
|
|
862
|
-
"ai.usage.promptTokens",
|
|
863
|
-
"ai.usage.input_tokens",
|
|
864
|
-
"ai.usage.inputTokens"
|
|
865
|
-
],
|
|
866
|
-
[MODEL_USAGE_ATTRIBUTES.outputTokens]: [
|
|
867
|
-
"gen_ai.usage.completion_tokens",
|
|
868
|
-
"ai.usage.completion_tokens",
|
|
869
|
-
"ai.usage.completionTokens",
|
|
870
|
-
"ai.usage.output_tokens",
|
|
871
|
-
"ai.usage.outputTokens"
|
|
872
|
-
],
|
|
873
|
-
[MODEL_USAGE_ATTRIBUTES.reasoningTokens]: [
|
|
874
|
-
"gen_ai.usage.reasoning.output_tokens",
|
|
875
|
-
"ai.usage.reasoningTokens",
|
|
876
|
-
"ai.usage.thoughts_tokens"
|
|
877
|
-
],
|
|
878
|
-
[MODEL_USAGE_ATTRIBUTES.cacheReadInputTokens]: [
|
|
879
|
-
"gen_ai.usage.cache_read_tokens",
|
|
880
|
-
"gen_ai.usage.cache_read.input_tokens",
|
|
881
|
-
"ai.usage.cachedInputTokens",
|
|
882
|
-
"ai.usage.cached_tokens",
|
|
883
|
-
"ai.usage.cache_read_tokens",
|
|
884
|
-
"ai.usage.cache_read_input_tokens",
|
|
885
|
-
"ai.usage.cacheReadInputTokens"
|
|
886
|
-
],
|
|
887
|
-
[MODEL_USAGE_ATTRIBUTES.cacheWriteInputTokens]: [
|
|
888
|
-
"gen_ai.usage.cache_creation_input_tokens",
|
|
889
|
-
"gen_ai.usage.cache_creation.input_tokens",
|
|
890
|
-
"ai.usage.cacheWriteInputTokens",
|
|
891
|
-
"ai.usage.cache_creation_input_tokens",
|
|
892
|
-
"ai.usage.cacheCreationInputTokens"
|
|
893
|
-
]
|
|
894
|
-
};
|
|
895
|
-
function hasAttribute(attributes, key) {
|
|
896
|
-
return attributes.some((attribute) => attribute.key === key);
|
|
897
|
-
}
|
|
898
|
-
function findStringAttribute(attributes, keys) {
|
|
899
|
-
return attributes.find(
|
|
900
|
-
(attribute) => keys.includes(attribute.key) && typeof attribute.value.stringValue === "string" && attribute.value.stringValue.length > 0
|
|
901
|
-
);
|
|
902
|
-
}
|
|
903
|
-
function findNumberAttribute(attributes, keys) {
|
|
904
|
-
return attributes.find((attribute) => {
|
|
905
|
-
if (!keys.includes(attribute.key)) return false;
|
|
906
|
-
if (typeof attribute.value.doubleValue === "number" && Number.isSafeInteger(attribute.value.doubleValue) && attribute.value.doubleValue >= 0) {
|
|
907
|
-
return true;
|
|
908
|
-
}
|
|
909
|
-
const intValue = attribute.value.intValue;
|
|
910
|
-
return integerValue(intValue) !== void 0;
|
|
911
|
-
});
|
|
912
|
-
}
|
|
913
|
-
function integerValue(value) {
|
|
914
|
-
if (typeof value === "number") {
|
|
915
|
-
return Number.isSafeInteger(value) && value >= 0 ? value : void 0;
|
|
916
|
-
}
|
|
917
|
-
if (typeof value !== "string") return void 0;
|
|
918
|
-
const parsed = Number(value);
|
|
919
|
-
return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : void 0;
|
|
920
|
-
}
|
|
921
|
-
function appendAlias(attributes, target, source) {
|
|
922
|
-
if (!source || hasAttribute(attributes, target)) return attributes;
|
|
923
|
-
return [...attributes, { key: target, value: { ...source.value } }];
|
|
924
|
-
}
|
|
925
|
-
function appendProviderAlias(attributes) {
|
|
926
|
-
if (hasAttribute(attributes, MODEL_USAGE_ATTRIBUTES.providerName)) return attributes;
|
|
927
|
-
const source = findStringAttribute(attributes, ["gen_ai.system", "ai.model.provider"]);
|
|
928
|
-
if (!(source == null ? void 0 : source.value.stringValue)) return attributes;
|
|
929
|
-
const attribute = attrString(
|
|
930
|
-
MODEL_USAGE_ATTRIBUTES.providerName,
|
|
931
|
-
canonicalModelProvider(source.value.stringValue)
|
|
932
|
-
);
|
|
933
|
-
return attribute ? [...attributes, attribute] : attributes;
|
|
934
|
-
}
|
|
935
|
-
function normalizeModelUsageSpan(span) {
|
|
936
|
-
const original = span.attributes;
|
|
937
|
-
if (!original || original.length === 0) return span;
|
|
938
|
-
const responseModel = findStringAttribute(original, [
|
|
939
|
-
MODEL_USAGE_ATTRIBUTES.responseModel,
|
|
940
|
-
"ai.response.model"
|
|
941
|
-
]);
|
|
942
|
-
if (!responseModel) return span;
|
|
943
|
-
let attributes = appendAlias(original, MODEL_USAGE_ATTRIBUTES.responseModel, responseModel);
|
|
944
|
-
attributes = appendProviderAlias(attributes);
|
|
945
|
-
for (const [target, aliases] of Object.entries(STRING_ALIASES)) {
|
|
946
|
-
attributes = appendAlias(attributes, target, findStringAttribute(attributes, aliases));
|
|
947
|
-
}
|
|
948
|
-
for (const [target, aliases] of Object.entries(NUMBER_ALIASES)) {
|
|
949
|
-
attributes = appendAlias(attributes, target, findNumberAttribute(attributes, aliases));
|
|
950
|
-
}
|
|
951
|
-
return attributes === original ? span : { ...span, attributes };
|
|
952
|
-
}
|
|
953
|
-
var DEFAULT_SECRET_KEY_NAMES = [
|
|
954
|
-
"apikey",
|
|
955
|
-
"apisecret",
|
|
956
|
-
"apitoken",
|
|
957
|
-
"secretaccesskey",
|
|
958
|
-
"sessiontoken",
|
|
959
|
-
"privatekey",
|
|
960
|
-
"privatekeyid",
|
|
961
|
-
"clientsecret",
|
|
962
|
-
"accesstoken",
|
|
963
|
-
"refreshtoken",
|
|
964
|
-
"oauthtoken",
|
|
965
|
-
"bearertoken",
|
|
966
|
-
"authorization",
|
|
967
|
-
"password",
|
|
968
|
-
"passphrase"
|
|
969
|
-
];
|
|
970
|
-
var REDACTED_PLACEHOLDER = "[REDACTED]";
|
|
971
|
-
function normalizeKeyName(name) {
|
|
972
|
-
return name.toLowerCase().replace(/[-_.]/g, "");
|
|
973
|
-
}
|
|
974
|
-
function redactSecretsInObject(value, options) {
|
|
975
|
-
var _a, _b;
|
|
976
|
-
const normalizedSecretSet = buildSecretSet((_a = options == null ? void 0 : options.secretKeyNames) != null ? _a : DEFAULT_SECRET_KEY_NAMES);
|
|
977
|
-
const placeholder = (_b = options == null ? void 0 : options.placeholder) != null ? _b : REDACTED_PLACEHOLDER;
|
|
978
|
-
const seen = /* @__PURE__ */ new WeakSet();
|
|
979
|
-
const walk = (node) => {
|
|
980
|
-
if (node === null || typeof node !== "object") return node;
|
|
981
|
-
if (seen.has(node)) return "[CIRCULAR]";
|
|
982
|
-
seen.add(node);
|
|
983
|
-
if (Array.isArray(node)) {
|
|
984
|
-
return node.map((item) => walk(item));
|
|
985
|
-
}
|
|
986
|
-
const out = {};
|
|
987
|
-
for (const [k, v] of Object.entries(node)) {
|
|
988
|
-
if (normalizedSecretSet.has(normalizeKeyName(k))) {
|
|
989
|
-
out[k] = placeholder;
|
|
990
|
-
} else {
|
|
991
|
-
out[k] = walk(v);
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
return out;
|
|
995
|
-
};
|
|
996
|
-
return walk(value);
|
|
997
|
-
}
|
|
998
|
-
function buildSecretSet(names) {
|
|
999
|
-
const set = /* @__PURE__ */ new Set();
|
|
1000
|
-
for (const name of names) set.add(normalizeKeyName(name));
|
|
1001
|
-
return set;
|
|
1002
|
-
}
|
|
1003
|
-
var DEFAULT_REDACT_ATTRIBUTE_KEYS = [
|
|
1004
|
-
"ai.request.providerOptions",
|
|
1005
|
-
"ai.response.providerMetadata"
|
|
1006
|
-
];
|
|
1007
|
-
function defaultTransformSpan(span) {
|
|
1008
|
-
const attrs = span.attributes;
|
|
1009
|
-
if (!attrs || attrs.length === 0) return span;
|
|
1010
|
-
let nextAttrs;
|
|
1011
|
-
for (let i = 0; i < attrs.length; i++) {
|
|
1012
|
-
const attr = attrs[i];
|
|
1013
|
-
const redacted = redactJsonAttributeValue(attr.key, attr.value);
|
|
1014
|
-
if (redacted === void 0) continue;
|
|
1015
|
-
if (!nextAttrs) nextAttrs = attrs.slice();
|
|
1016
|
-
nextAttrs[i] = { key: attr.key, value: redacted };
|
|
1017
|
-
}
|
|
1018
|
-
if (!nextAttrs) return span;
|
|
1019
|
-
return { ...span, attributes: nextAttrs };
|
|
1020
|
-
}
|
|
1021
|
-
var REDACT_JSON_ATTRIBUTE_KEYS = new Set(DEFAULT_REDACT_ATTRIBUTE_KEYS);
|
|
1022
|
-
function redactJsonAttributeValue(key, value) {
|
|
1023
|
-
if (!REDACT_JSON_ATTRIBUTE_KEYS.has(key)) return void 0;
|
|
1024
|
-
const json = value.stringValue;
|
|
1025
|
-
if (typeof json !== "string" || json.length === 0) return void 0;
|
|
1026
|
-
let parsed;
|
|
1027
|
-
try {
|
|
1028
|
-
parsed = JSON.parse(json);
|
|
1029
|
-
} catch (e) {
|
|
1030
|
-
return void 0;
|
|
1031
|
-
}
|
|
1032
|
-
const scrubbed = redactSecretsInObject(parsed);
|
|
1033
|
-
let scrubbedJson;
|
|
1034
|
-
try {
|
|
1035
|
-
scrubbedJson = JSON.stringify(scrubbed);
|
|
1036
|
-
} catch (e) {
|
|
1037
|
-
return void 0;
|
|
1038
|
-
}
|
|
1039
|
-
if (scrubbedJson === json) return void 0;
|
|
1040
|
-
return { stringValue: scrubbedJson };
|
|
1041
|
-
}
|
|
1042
|
-
function identityAttrs(args) {
|
|
1043
|
-
return [
|
|
1044
|
-
attrString("ai.telemetry.metadata.raindrop.ai.userId", args.userId),
|
|
1045
|
-
attrString("ai.telemetry.metadata.raindrop.convoId", args.convoId),
|
|
1046
|
-
attrString("ai.telemetry.metadata.raindrop.eventName", args.eventName)
|
|
1047
|
-
];
|
|
1048
|
-
}
|
|
1049
|
-
function applyOtelSpanAttributeLimit(limit) {
|
|
1050
|
-
var _a, _b;
|
|
1051
|
-
try {
|
|
1052
|
-
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;
|
|
1053
|
-
if (!raw) return limit;
|
|
1054
|
-
const parsed = Number.parseInt(raw, 10);
|
|
1055
|
-
if (Number.isFinite(parsed) && parsed > 0) {
|
|
1056
|
-
return Math.min(limit, parsed);
|
|
1057
|
-
}
|
|
1058
|
-
} catch (e) {
|
|
1059
|
-
}
|
|
1060
|
-
return limit;
|
|
1061
|
-
}
|
|
1062
|
-
var TraceShipper = class {
|
|
1063
|
-
constructor(opts) {
|
|
1064
|
-
this.queue = [];
|
|
1065
|
-
this.inFlight = /* @__PURE__ */ new Set();
|
|
1066
|
-
this.hasShutdown = false;
|
|
1067
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
1068
|
-
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
1069
|
-
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
1070
|
-
this.enabled = opts.enabled !== false;
|
|
1071
|
-
this.debug = opts.debug;
|
|
1072
|
-
this.debugSpans = opts.debugSpans === true;
|
|
1073
|
-
this.flushIntervalMs = (_c = opts.flushIntervalMs) != null ? _c : 1e3;
|
|
1074
|
-
this.maxBatchSize = (_d = opts.maxBatchSize) != null ? _d : 50;
|
|
1075
|
-
this.maxQueueSize = (_e = opts.maxQueueSize) != null ? _e : 5e3;
|
|
1076
|
-
this.sdkName = (_f = opts.sdkName) != null ? _f : "core";
|
|
1077
|
-
this.prefix = `[raindrop-ai/${this.sdkName}]`;
|
|
1078
|
-
this.serviceName = (_g = opts.serviceName) != null ? _g : "raindrop.core";
|
|
1079
|
-
this.serviceVersion = (_h = opts.serviceVersion) != null ? _h : "0.0.0";
|
|
1080
|
-
this.localDebuggerUrl = (_i = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _i : void 0;
|
|
1081
|
-
if (this.debug && this.localDebuggerUrl) {
|
|
1082
|
-
console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
|
|
1083
|
-
}
|
|
1084
|
-
this.projectId = normalizeProjectId(opts.projectId, {
|
|
1085
|
-
debug: this.debug,
|
|
1086
|
-
prefix: this.prefix
|
|
1087
|
-
});
|
|
1088
|
-
this.transformSpanHook = opts.transformSpan;
|
|
1089
|
-
this.disableDefaultRedaction = opts.disableDefaultRedaction === true;
|
|
1090
|
-
this.maxTextFieldCharsOpt = opts.maxTextFieldChars;
|
|
1091
|
-
}
|
|
1092
|
-
/**
|
|
1093
|
-
* Cap every string attribute value on the span. O(#attributes) length
|
|
1094
|
-
* checks; only oversized values pay a slice. Runs AFTER the redaction
|
|
1095
|
-
* pipeline so the default secret-scrub still sees parseable JSON in
|
|
1096
|
-
* `ai.request.providerOptions` / `ai.response.providerMetadata` (capping
|
|
1097
|
-
* first could cut a JSON blob mid-way, fail the parse, and ship secrets
|
|
1098
|
-
* in the surviving prefix).
|
|
1099
|
-
*
|
|
1100
|
-
* A stricter `OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT` env var is honored
|
|
1101
|
-
* for span content, matching the Python SDK and the OTel SDK convention.
|
|
1102
|
-
*/
|
|
1103
|
-
capSpanAttributes(span) {
|
|
1104
|
-
var _a;
|
|
1105
|
-
const maxChars = applyOtelSpanAttributeLimit(
|
|
1106
|
-
resolveMaxTextFieldChars(this.maxTextFieldCharsOpt)
|
|
1107
|
-
);
|
|
1108
|
-
const attrs = span.attributes;
|
|
1109
|
-
if (!attrs || attrs.length === 0) return span;
|
|
1110
|
-
let nextAttrs;
|
|
1111
|
-
for (let i = 0; i < attrs.length; i++) {
|
|
1112
|
-
const attr = attrs[i];
|
|
1113
|
-
const value = (_a = attr.value) == null ? void 0 : _a.stringValue;
|
|
1114
|
-
if (typeof value !== "string" || value.length <= maxChars) continue;
|
|
1115
|
-
if (!nextAttrs) nextAttrs = attrs.slice();
|
|
1116
|
-
nextAttrs[i] = {
|
|
1117
|
-
key: attr.key,
|
|
1118
|
-
value: { ...attr.value, stringValue: capText(value, maxChars) }
|
|
1119
|
-
};
|
|
1120
|
-
}
|
|
1121
|
-
if (!nextAttrs) return span;
|
|
1122
|
-
return { ...span, attributes: nextAttrs };
|
|
1123
|
-
}
|
|
1124
|
-
/**
|
|
1125
|
-
* Apply the user `transformSpan` hook (if any) followed by the default
|
|
1126
|
-
* redactor (unless disabled). Returns either the (possibly new) span to
|
|
1127
|
-
* ship, or `null` to drop the span entirely.
|
|
1128
|
-
*
|
|
1129
|
-
* Ordering: user hook runs first so callers can rewrite the span freely
|
|
1130
|
-
* (rename attrs, add new ones, scrub things the default doesn't know
|
|
1131
|
-
* about). The default redactor then runs on whatever the user produced,
|
|
1132
|
-
* acting as the always-on floor for documented BYOK secrets. If the user
|
|
1133
|
-
* sets `disableDefaultRedaction: true`, the floor is skipped.
|
|
1134
|
-
*
|
|
1135
|
-
* Fail-closed: if the user hook throws, the span is dropped — a buggy
|
|
1136
|
-
* hook can never accidentally ship raw, un-redacted spans.
|
|
1137
|
-
*/
|
|
1138
|
-
redactSpan(span) {
|
|
1139
|
-
let current = span;
|
|
1140
|
-
if (this.transformSpanHook) {
|
|
1141
|
-
try {
|
|
1142
|
-
const result = this.transformSpanHook(current);
|
|
1143
|
-
if (result === null) return null;
|
|
1144
|
-
if (result !== void 0) current = result;
|
|
1145
|
-
} catch (err) {
|
|
1146
|
-
if (this.debug) {
|
|
1147
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1148
|
-
console.warn(`${this.prefix} transformSpan hook threw: ${msg}`);
|
|
1149
|
-
}
|
|
1150
|
-
return null;
|
|
1151
|
-
}
|
|
1152
|
-
}
|
|
1153
|
-
try {
|
|
1154
|
-
current = normalizeModelUsageSpan(current);
|
|
1155
|
-
} catch (e) {
|
|
1156
|
-
}
|
|
1157
|
-
if (!this.disableDefaultRedaction) {
|
|
1158
|
-
current = defaultTransformSpan(current);
|
|
1159
|
-
}
|
|
1160
|
-
return this.capSpanAttributes(current);
|
|
1161
|
-
}
|
|
1162
|
-
isDebugEnabled() {
|
|
1163
|
-
return this.debug;
|
|
1164
|
-
}
|
|
1165
|
-
authHeaders() {
|
|
1166
|
-
return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
|
|
1167
|
-
}
|
|
1168
|
-
requestHeaders() {
|
|
1169
|
-
return { ...this.authHeaders(), ...projectIdHeaders(this.projectId) };
|
|
1170
|
-
}
|
|
1171
|
-
startSpan(args) {
|
|
1172
|
-
var _a, _b;
|
|
1173
|
-
const ids = createSpanIds(args.parent);
|
|
1174
|
-
const started = (_a = args.startTimeUnixNano) != null ? _a : nowUnixNanoString();
|
|
1175
|
-
const attrs = [
|
|
1176
|
-
attrString("ai.telemetry.metadata.raindrop.eventId", args.eventId),
|
|
1177
|
-
...identityAttrs(args),
|
|
1178
|
-
attrString("ai.operationId", args.operationId)
|
|
1179
|
-
];
|
|
1180
|
-
if ((_b = args.attributes) == null ? void 0 : _b.length) attrs.push(...args.attributes);
|
|
1181
|
-
const span = { ids, name: args.name, startTimeUnixNano: started, attributes: attrs };
|
|
1182
|
-
this.mirrorToLocalDebugger(
|
|
1183
|
-
buildOtlpSpan({
|
|
1184
|
-
ids: span.ids,
|
|
1185
|
-
name: span.name,
|
|
1186
|
-
startTimeUnixNano: span.startTimeUnixNano,
|
|
1187
|
-
endTimeUnixNano: span.startTimeUnixNano,
|
|
1188
|
-
// placeholder — will be updated on endSpan
|
|
1189
|
-
attributes: span.attributes,
|
|
1190
|
-
status: { code: SpanStatusCode.UNSET }
|
|
1191
|
-
})
|
|
1192
|
-
);
|
|
1193
|
-
return span;
|
|
1194
|
-
}
|
|
1195
|
-
mirrorToLocalDebugger(span) {
|
|
1196
|
-
if (!this.localDebuggerUrl) return;
|
|
1197
|
-
const redacted = this.redactSpan(span);
|
|
1198
|
-
if (redacted === null) return;
|
|
1199
|
-
const body = buildExportTraceServiceRequest([redacted], this.serviceName, this.serviceVersion);
|
|
1200
|
-
mirrorTraceExportToLocalDebugger(body, {
|
|
1201
|
-
baseUrl: this.localDebuggerUrl,
|
|
1202
|
-
debug: false,
|
|
1203
|
-
sdkName: this.sdkName
|
|
1204
|
-
});
|
|
1205
|
-
}
|
|
1206
|
-
endSpan(span, extra) {
|
|
1207
|
-
var _a, _b;
|
|
1208
|
-
if (span.endTimeUnixNano) return;
|
|
1209
|
-
span.endTimeUnixNano = (_a = extra == null ? void 0 : extra.endTimeUnixNano) != null ? _a : nowUnixNanoString();
|
|
1210
|
-
if ((_b = extra == null ? void 0 : extra.attributes) == null ? void 0 : _b.length) {
|
|
1211
|
-
span.attributes.push(...extra.attributes);
|
|
1212
|
-
}
|
|
1213
|
-
let status = extra == null ? void 0 : extra.status;
|
|
1214
|
-
if (!status && (extra == null ? void 0 : extra.error) !== void 0) {
|
|
1215
|
-
const message = extra.error instanceof Error ? extra.error.message : String(extra.error);
|
|
1216
|
-
status = { code: SpanStatusCode.ERROR, message };
|
|
1217
|
-
}
|
|
1218
|
-
const otlp = buildOtlpSpan({
|
|
1219
|
-
ids: span.ids,
|
|
1220
|
-
name: span.name,
|
|
1221
|
-
startTimeUnixNano: span.startTimeUnixNano,
|
|
1222
|
-
endTimeUnixNano: span.endTimeUnixNano,
|
|
1223
|
-
attributes: span.attributes,
|
|
1224
|
-
status
|
|
1225
|
-
});
|
|
1226
|
-
this.enqueue(otlp);
|
|
1227
|
-
this.mirrorToLocalDebugger(otlp);
|
|
1228
|
-
}
|
|
1229
|
-
createSpan(args) {
|
|
1230
|
-
var _a;
|
|
1231
|
-
const ids = createSpanIds(args.parent);
|
|
1232
|
-
const attrs = [
|
|
1233
|
-
attrString("ai.telemetry.metadata.raindrop.eventId", args.eventId),
|
|
1234
|
-
...identityAttrs(args)
|
|
1235
|
-
];
|
|
1236
|
-
if ((_a = args.attributes) == null ? void 0 : _a.length) attrs.push(...args.attributes);
|
|
1237
|
-
const otlp = buildOtlpSpan({
|
|
1238
|
-
ids,
|
|
1239
|
-
name: args.name,
|
|
1240
|
-
startTimeUnixNano: args.startTimeUnixNano,
|
|
1241
|
-
endTimeUnixNano: args.endTimeUnixNano,
|
|
1242
|
-
attributes: attrs,
|
|
1243
|
-
status: args.status
|
|
1244
|
-
});
|
|
1245
|
-
this.enqueue(otlp);
|
|
1246
|
-
this.mirrorToLocalDebugger(otlp);
|
|
1247
|
-
}
|
|
1248
|
-
enqueue(span) {
|
|
1249
|
-
if (!this.enabled) return;
|
|
1250
|
-
if (this.debugSpans) {
|
|
1251
|
-
const short = (s) => s ? s.slice(-8) : "none";
|
|
1252
|
-
console.log(
|
|
1253
|
-
`${this.prefix}[span] name=${span.name} trace=${short(span.traceId)} span=${short(span.spanId)} parent=${short(
|
|
1254
|
-
span.parentSpanId
|
|
1255
|
-
)}`
|
|
1256
|
-
);
|
|
1257
|
-
}
|
|
1258
|
-
const redacted = this.redactSpan(span);
|
|
1259
|
-
if (redacted === null) return;
|
|
1260
|
-
if (this.queue.length >= this.maxQueueSize) {
|
|
1261
|
-
this.queue.shift();
|
|
1262
|
-
}
|
|
1263
|
-
this.queue.push(redacted);
|
|
1264
|
-
if (this.queue.length >= this.maxBatchSize) {
|
|
1265
|
-
void this.flush().catch(() => {
|
|
1266
|
-
});
|
|
1267
|
-
return;
|
|
1268
|
-
}
|
|
1269
|
-
if (!this.timer) {
|
|
1270
|
-
this.timer = setTimeout(() => {
|
|
1271
|
-
this.timer = void 0;
|
|
1272
|
-
void this.flush().catch(() => {
|
|
1273
|
-
});
|
|
1274
|
-
}, this.flushIntervalMs);
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
async flush() {
|
|
1278
|
-
if (!this.enabled) return;
|
|
1279
|
-
if (this.timer) {
|
|
1280
|
-
clearTimeout(this.timer);
|
|
1281
|
-
this.timer = void 0;
|
|
1282
|
-
}
|
|
1283
|
-
while (this.queue.length > 0) {
|
|
1284
|
-
const batch = this.queue.splice(0, this.maxBatchSize);
|
|
1285
|
-
if (!this.writeKey) continue;
|
|
1286
|
-
const opts = this.requestOpts();
|
|
1287
|
-
if (!opts) {
|
|
1288
|
-
rateLimitedLog(
|
|
1289
|
-
`${this.prefix}.shutdown_deadline`,
|
|
1290
|
-
() => console.warn(
|
|
1291
|
-
`${this.prefix} shutdown flush deadline exceeded; dropping ${batch.length} spans`
|
|
1292
|
-
)
|
|
1293
|
-
);
|
|
1294
|
-
continue;
|
|
1295
|
-
}
|
|
1296
|
-
const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
|
|
1297
|
-
const url = `${this.baseUrl}traces`;
|
|
1298
|
-
if (this.debug) {
|
|
1299
|
-
console.log(`${this.prefix} sending traces batch`, {
|
|
1300
|
-
spans: batch.length,
|
|
1301
|
-
endpoint: url
|
|
1302
|
-
});
|
|
1303
|
-
}
|
|
1304
|
-
const p = postJson(url, body, this.requestHeaders(), opts);
|
|
1305
|
-
this.inFlight.add(p);
|
|
1306
|
-
try {
|
|
1307
|
-
try {
|
|
1308
|
-
await p;
|
|
1309
|
-
if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
|
|
1310
|
-
} catch (err) {
|
|
1311
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1312
|
-
rateLimitedLog(
|
|
1313
|
-
`${this.prefix}.send_spans_failed`,
|
|
1314
|
-
() => console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`)
|
|
1315
|
-
);
|
|
1316
|
-
}
|
|
1317
|
-
} finally {
|
|
1318
|
-
this.inFlight.delete(p);
|
|
1319
|
-
}
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
/** See EventShipper.requestOpts — same shutdown-budget semantics. */
|
|
1323
|
-
requestOpts() {
|
|
1324
|
-
if (this.shutdownDeadlineAt !== void 0) {
|
|
1325
|
-
const remainingMs = this.shutdownDeadlineAt - Date.now();
|
|
1326
|
-
if (remainingMs <= 0) return null;
|
|
1327
|
-
return {
|
|
1328
|
-
maxAttempts: 1,
|
|
1329
|
-
debug: this.debug,
|
|
1330
|
-
sdkName: this.sdkName,
|
|
1331
|
-
timeoutMs: Math.min(DEFAULT_REQUEST_TIMEOUT_MS, remainingMs)
|
|
1332
|
-
};
|
|
1333
|
-
}
|
|
1334
|
-
if (this.hasShutdown) {
|
|
1335
|
-
return {
|
|
1336
|
-
maxAttempts: 1,
|
|
1337
|
-
debug: this.debug,
|
|
1338
|
-
sdkName: this.sdkName,
|
|
1339
|
-
timeoutMs: POST_SHUTDOWN_TIMEOUT_MS
|
|
1340
|
-
};
|
|
1341
|
-
}
|
|
1342
|
-
return { maxAttempts: 3, debug: this.debug, sdkName: this.sdkName };
|
|
1343
|
-
}
|
|
1344
|
-
async shutdown() {
|
|
1345
|
-
this.hasShutdown = true;
|
|
1346
|
-
this.shutdownDeadlineAt = Date.now() + SHUTDOWN_DEADLINE_MS;
|
|
1347
|
-
try {
|
|
1348
|
-
if (this.timer) {
|
|
1349
|
-
clearTimeout(this.timer);
|
|
1350
|
-
this.timer = void 0;
|
|
1351
|
-
}
|
|
1352
|
-
const drain = async () => {
|
|
1353
|
-
await this.flush();
|
|
1354
|
-
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
1355
|
-
})));
|
|
1356
|
-
};
|
|
1357
|
-
const settled = await raceWithTimeout(drain(), SHUTDOWN_DEADLINE_MS);
|
|
1358
|
-
if (!settled) {
|
|
1359
|
-
rateLimitedLog(
|
|
1360
|
-
`${this.prefix}.shutdown_deadline`,
|
|
1361
|
-
() => console.warn(`${this.prefix} shutdown flush deadline exceeded; abandoning in-flight spans`)
|
|
1362
|
-
);
|
|
1363
|
-
}
|
|
1364
|
-
} finally {
|
|
1365
|
-
this.shutdownDeadlineAt = void 0;
|
|
1366
|
-
}
|
|
1367
|
-
}
|
|
1368
|
-
};
|
|
1369
|
-
|
|
1370
|
-
// ../core/dist/index.node.js
|
|
1371
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
1372
|
-
globalThis.RAINDROP_ASYNC_LOCAL_STORAGE = AsyncLocalStorage;
|
|
1373
|
-
var SUPPRESS_TRACING_KEY = /* @__PURE__ */ Symbol.for(
|
|
1374
|
-
"OpenTelemetry SDK Context Key SUPPRESS_TRACING"
|
|
1375
|
-
);
|
|
1376
|
-
function findOtelContextManager() {
|
|
1377
|
-
var _a;
|
|
1378
|
-
for (const sym of Object.getOwnPropertySymbols(globalThis)) {
|
|
1379
|
-
if (!((_a = sym.description) == null ? void 0 : _a.startsWith("opentelemetry.js.api."))) continue;
|
|
1380
|
-
const api = globalThis[sym];
|
|
1381
|
-
const cm = api == null ? void 0 : api.context;
|
|
1382
|
-
if (cm && typeof cm.with === "function" && typeof cm.active === "function") {
|
|
1383
|
-
return cm;
|
|
1384
|
-
}
|
|
1385
|
-
}
|
|
1386
|
-
return void 0;
|
|
1387
|
-
}
|
|
1388
|
-
function installTracingSuppressionHook() {
|
|
1389
|
-
if (typeof globalThis.RAINDROP_SUPPRESS_TRACING === "function") return;
|
|
1390
|
-
const hook = (fn) => {
|
|
1391
|
-
const cm = findOtelContextManager();
|
|
1392
|
-
if (!cm) return fn();
|
|
1393
|
-
return cm.with(cm.active().setValue(SUPPRESS_TRACING_KEY, true), fn);
|
|
1394
|
-
};
|
|
1395
|
-
globalThis.RAINDROP_SUPPRESS_TRACING = hook;
|
|
1396
|
-
}
|
|
1397
|
-
installTracingSuppressionHook();
|
|
1398
|
-
|
|
1399
|
-
// src/config.ts
|
|
1400
|
-
import { existsSync, readFileSync } from "fs";
|
|
1401
|
-
import { homedir } from "os";
|
|
1402
|
-
import { join } from "path";
|
|
1403
|
-
function loadConfig(projectDirectory) {
|
|
1404
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1405
|
-
let merged = {};
|
|
1406
|
-
const configPaths = [
|
|
1407
|
-
join(homedir(), ".config", "opencode", "raindrop.json"),
|
|
1408
|
-
join(projectDirectory, ".opencode", "raindrop.json")
|
|
1409
|
-
];
|
|
1410
|
-
for (const configPath of configPaths) {
|
|
1411
|
-
try {
|
|
1412
|
-
if (existsSync(configPath)) {
|
|
1413
|
-
const content = readFileSync(configPath, "utf-8");
|
|
1414
|
-
const parsed = JSON.parse(content);
|
|
1415
|
-
merged = { ...merged, ...parsed };
|
|
1416
|
-
}
|
|
1417
|
-
} catch (e) {
|
|
1418
|
-
}
|
|
1419
|
-
}
|
|
1420
|
-
let eventMetadata;
|
|
1421
|
-
const envMeta = process.env["RAINDROP_EVENT_METADATA"];
|
|
1422
|
-
if (envMeta) {
|
|
1423
|
-
try {
|
|
1424
|
-
eventMetadata = JSON.parse(envMeta);
|
|
1425
|
-
} catch (e) {
|
|
1426
|
-
}
|
|
1427
|
-
}
|
|
1428
|
-
return {
|
|
1429
|
-
writeKey: (_b = (_a = process.env["RAINDROP_WRITE_KEY"]) != null ? _a : merged.write_key) != null ? _b : "",
|
|
1430
|
-
endpoint: (_d = (_c = process.env["RAINDROP_API_URL"]) != null ? _c : merged.api_url) != null ? _d : "https://api.raindrop.ai/v1",
|
|
1431
|
-
projectId: (_e = process.env["RAINDROP_PROJECT_ID"]) != null ? _e : merged.project_id,
|
|
1432
|
-
eventName: (_f = merged.event_name) != null ? _f : "opencode_session",
|
|
1433
|
-
debug: process.env["RAINDROP_DEBUG"] === "true" ? true : (_g = merged.debug) != null ? _g : false,
|
|
1434
|
-
captureSystemPrompt: process.env["RAINDROP_CAPTURE_SYSTEM_PROMPT"] !== void 0 ? process.env["RAINDROP_CAPTURE_SYSTEM_PROMPT"] === "true" : (_h = merged.capture_system_prompt) != null ? _h : false,
|
|
1435
|
-
eventMetadata,
|
|
1436
|
-
localWorkshopUrl: resolveLocalWorkshopUrl(merged.local_workshop_url)
|
|
1437
|
-
};
|
|
1438
|
-
}
|
|
1439
|
-
function resolveLocalWorkshopUrl(fileValue) {
|
|
1440
|
-
const envValue = process.env["RAINDROP_LOCAL_WORKSHOP_URL"];
|
|
1441
|
-
if (envValue !== void 0) {
|
|
1442
|
-
if (envValue === "" || envValue.toLowerCase() === "null" || envValue.toLowerCase() === "false") {
|
|
1443
|
-
return null;
|
|
1444
|
-
}
|
|
1445
|
-
return envValue;
|
|
1446
|
-
}
|
|
1447
|
-
return fileValue;
|
|
1448
|
-
}
|
|
1449
|
-
|
|
1450
|
-
// package.json
|
|
1451
|
-
var package_default = {
|
|
1452
|
-
name: "@raindrop-ai/opencode-plugin",
|
|
1453
|
-
version: "0.1.3",
|
|
1454
|
-
description: "Raindrop observability plugin for OpenCode \u2014 automatic session/event/span tracing",
|
|
1455
|
-
type: "module",
|
|
1456
|
-
main: "dist/index.js",
|
|
1457
|
-
module: "dist/index.js",
|
|
1458
|
-
types: "dist/index.d.ts",
|
|
1459
|
-
license: "MIT",
|
|
1460
|
-
homepage: "https://www.raindrop.ai/docs/integrations/opencode/",
|
|
1461
|
-
bugs: {
|
|
1462
|
-
url: "https://www.raindrop.ai/docs/support/"
|
|
1463
|
-
},
|
|
1464
|
-
exports: {
|
|
1465
|
-
".": {
|
|
1466
|
-
types: "./dist/index.d.ts",
|
|
1467
|
-
import: "./dist/index.js",
|
|
1468
|
-
require: "./dist/index.cjs"
|
|
1469
|
-
}
|
|
1470
|
-
},
|
|
1471
|
-
sideEffects: false,
|
|
1472
|
-
files: [
|
|
1473
|
-
"dist/**"
|
|
1474
|
-
],
|
|
1475
|
-
scripts: {
|
|
1476
|
-
build: "tsup",
|
|
1477
|
-
dev: "tsup --watch",
|
|
1478
|
-
clean: "rm -rf dist",
|
|
1479
|
-
test: "cd tests && pnpm test"
|
|
1480
|
-
},
|
|
1481
|
-
peerDependencies: {
|
|
1482
|
-
"@opencode-ai/plugin": ">=1.3.0",
|
|
1483
|
-
"@opencode-ai/sdk": ">=1.3.0"
|
|
1484
|
-
},
|
|
1485
|
-
peerDependenciesMeta: {
|
|
1486
|
-
"@opencode-ai/sdk": {
|
|
1487
|
-
optional: true
|
|
1488
|
-
}
|
|
1489
|
-
},
|
|
1490
|
-
devDependencies: {
|
|
1491
|
-
"@raindrop-ai/core": "workspace:*",
|
|
1492
|
-
"@opencode-ai/plugin": "^1.3.3",
|
|
1493
|
-
"@opencode-ai/sdk": "^1.3.3",
|
|
1494
|
-
"@types/node": "^20.11.17",
|
|
1495
|
-
tsup: "^8.4.0",
|
|
1496
|
-
typescript: "^5.3.3"
|
|
1497
|
-
},
|
|
1498
|
-
tsup: {
|
|
1499
|
-
entry: [
|
|
1500
|
-
"src/index.ts"
|
|
1501
|
-
],
|
|
1502
|
-
format: [
|
|
1503
|
-
"cjs",
|
|
1504
|
-
"esm"
|
|
1505
|
-
],
|
|
1506
|
-
dts: {
|
|
1507
|
-
resolve: true
|
|
1508
|
-
},
|
|
1509
|
-
clean: true,
|
|
1510
|
-
noExternal: [
|
|
1511
|
-
"@raindrop-ai/core"
|
|
1512
|
-
]
|
|
1513
|
-
},
|
|
1514
|
-
publishConfig: {
|
|
1515
|
-
access: "public"
|
|
1516
|
-
}
|
|
1517
|
-
};
|
|
1518
|
-
|
|
1519
|
-
// src/package-info.ts
|
|
1520
|
-
var PLUGIN_NAME = package_default.name;
|
|
1521
|
-
var PLUGIN_VERSION = package_default.version;
|
|
1522
|
-
|
|
1523
|
-
// src/shipper.ts
|
|
1524
|
-
var EventShipper2 = class extends EventShipper {
|
|
1525
|
-
constructor(opts) {
|
|
1526
|
-
var _a, _b, _c, _d;
|
|
1527
|
-
super({
|
|
1528
|
-
...opts,
|
|
1529
|
-
sdkName: (_a = opts.sdkName) != null ? _a : "opencode-plugin",
|
|
1530
|
-
libraryName: (_b = opts.libraryName) != null ? _b : PLUGIN_NAME,
|
|
1531
|
-
libraryVersion: (_c = opts.libraryVersion) != null ? _c : PLUGIN_VERSION,
|
|
1532
|
-
defaultEventName: (_d = opts.defaultEventName) != null ? _d : "opencode_session"
|
|
1533
|
-
});
|
|
1534
|
-
}
|
|
1535
|
-
};
|
|
1536
|
-
var TraceShipper2 = class extends TraceShipper {
|
|
1537
|
-
constructor(opts) {
|
|
1538
|
-
var _a, _b, _c;
|
|
1539
|
-
super({
|
|
1540
|
-
...opts,
|
|
1541
|
-
sdkName: (_a = opts.sdkName) != null ? _a : "opencode-plugin",
|
|
1542
|
-
serviceName: (_b = opts.serviceName) != null ? _b : "raindrop.opencode-plugin",
|
|
1543
|
-
serviceVersion: (_c = opts.serviceVersion) != null ? _c : PLUGIN_VERSION
|
|
1544
|
-
});
|
|
1545
|
-
}
|
|
1546
|
-
enqueue(span) {
|
|
1547
|
-
var _a;
|
|
1548
|
-
const attrs = (_a = span.attributes) != null ? _a : [];
|
|
1549
|
-
attrs.unshift(
|
|
1550
|
-
{ key: "span.id", value: { stringValue: span.spanId } },
|
|
1551
|
-
...span.parentSpanId ? [{ key: "span.parent.id", value: { stringValue: span.parentSpanId } }] : []
|
|
1552
|
-
);
|
|
1553
|
-
span.attributes = attrs;
|
|
1554
|
-
super.enqueue(span);
|
|
1555
|
-
}
|
|
1556
|
-
};
|
|
1
|
+
import {
|
|
2
|
+
EventShipper,
|
|
3
|
+
PLUGIN_NAME,
|
|
4
|
+
PLUGIN_VERSION,
|
|
5
|
+
TraceShipper,
|
|
6
|
+
attrString,
|
|
7
|
+
boundedStringify,
|
|
8
|
+
buildLlmGenAiAttributes,
|
|
9
|
+
capText,
|
|
10
|
+
createAppGitContext,
|
|
11
|
+
generateId,
|
|
12
|
+
loadConfig,
|
|
13
|
+
nowUnixNanoString,
|
|
14
|
+
resolveLocalDebuggerBaseUrl
|
|
15
|
+
} from "./chunk-4DAI6CYK.js";
|
|
1557
16
|
|
|
1558
17
|
// src/tracing.ts
|
|
1559
18
|
import { hostname as osHostname } from "os";
|
|
1560
19
|
|
|
1561
|
-
// src/bounded.ts
|
|
1562
|
-
var TRUNCATION_MARKER2 = "...[truncated by raindrop]";
|
|
1563
|
-
var MAX_TEXT_FIELD_CHARS = 1e6;
|
|
1564
|
-
var MAX_BOUNDED_DEPTH = 12;
|
|
1565
|
-
function truncateToLimit2(text, limit) {
|
|
1566
|
-
if (text.length <= limit) return text;
|
|
1567
|
-
if (limit > TRUNCATION_MARKER2.length) {
|
|
1568
|
-
return text.slice(0, limit - TRUNCATION_MARKER2.length) + TRUNCATION_MARKER2;
|
|
1569
|
-
}
|
|
1570
|
-
return text.slice(0, limit);
|
|
1571
|
-
}
|
|
1572
|
-
function capText2(value, limit = MAX_TEXT_FIELD_CHARS) {
|
|
1573
|
-
if (value.length <= limit) return value;
|
|
1574
|
-
return truncateToLimit2(value, limit);
|
|
1575
|
-
}
|
|
1576
|
-
function boundedClone2(value, budget, depth) {
|
|
1577
|
-
if (budget.remaining <= 0) return TRUNCATION_MARKER2;
|
|
1578
|
-
if (typeof value === "string") {
|
|
1579
|
-
if (value.length > budget.remaining) {
|
|
1580
|
-
const taken = value.slice(0, Math.max(0, budget.remaining)) + TRUNCATION_MARKER2;
|
|
1581
|
-
budget.remaining = 0;
|
|
1582
|
-
return taken;
|
|
1583
|
-
}
|
|
1584
|
-
budget.remaining -= Math.max(value.length, 1);
|
|
1585
|
-
return value;
|
|
1586
|
-
}
|
|
1587
|
-
if (value === null || typeof value === "number" || typeof value === "boolean") {
|
|
1588
|
-
budget.remaining -= 8;
|
|
1589
|
-
return value;
|
|
1590
|
-
}
|
|
1591
|
-
if (typeof value !== "object") {
|
|
1592
|
-
budget.remaining -= 8;
|
|
1593
|
-
return value;
|
|
1594
|
-
}
|
|
1595
|
-
if (depth >= MAX_BOUNDED_DEPTH) {
|
|
1596
|
-
budget.remaining -= 16;
|
|
1597
|
-
return `<max depth: ${TRUNCATION_MARKER2}>`;
|
|
1598
|
-
}
|
|
1599
|
-
const withToJson = value;
|
|
1600
|
-
if (typeof withToJson.toJSON === "function") {
|
|
1601
|
-
try {
|
|
1602
|
-
return boundedClone2(withToJson.toJSON(), budget, depth + 1);
|
|
1603
|
-
} catch (e) {
|
|
1604
|
-
}
|
|
1605
|
-
}
|
|
1606
|
-
if (Array.isArray(value)) {
|
|
1607
|
-
const out2 = [];
|
|
1608
|
-
for (const item of value) {
|
|
1609
|
-
if (budget.remaining <= 0) {
|
|
1610
|
-
out2.push(TRUNCATION_MARKER2);
|
|
1611
|
-
break;
|
|
1612
|
-
}
|
|
1613
|
-
out2.push(boundedClone2(item, budget, depth + 1));
|
|
1614
|
-
}
|
|
1615
|
-
return out2;
|
|
1616
|
-
}
|
|
1617
|
-
const out = {};
|
|
1618
|
-
for (const key of Object.keys(value)) {
|
|
1619
|
-
if (budget.remaining <= 0) {
|
|
1620
|
-
out["..."] = TRUNCATION_MARKER2;
|
|
1621
|
-
break;
|
|
1622
|
-
}
|
|
1623
|
-
budget.remaining -= Math.max(key.length, 1);
|
|
1624
|
-
out[key] = boundedClone2(value[key], budget, depth + 1);
|
|
1625
|
-
}
|
|
1626
|
-
return out;
|
|
1627
|
-
}
|
|
1628
|
-
function boundedStringify(value, limit = MAX_TEXT_FIELD_CHARS) {
|
|
1629
|
-
var _a;
|
|
1630
|
-
try {
|
|
1631
|
-
if (typeof value === "string") {
|
|
1632
|
-
return truncateToLimit2(JSON.stringify(capText2(value, limit)), limit);
|
|
1633
|
-
}
|
|
1634
|
-
const pruned = boundedClone2(value, { remaining: limit + TRUNCATION_MARKER2.length + 256 }, 0);
|
|
1635
|
-
return truncateToLimit2((_a = JSON.stringify(pruned)) != null ? _a : "", limit);
|
|
1636
|
-
} catch (e) {
|
|
1637
|
-
return capText2(String(value), limit);
|
|
1638
|
-
}
|
|
1639
|
-
}
|
|
1640
|
-
|
|
1641
|
-
// src/model-spend.ts
|
|
1642
|
-
function parseOpenCodeAssistantTokens(tokens) {
|
|
1643
|
-
const input = tokens["input"];
|
|
1644
|
-
const output = tokens["output"];
|
|
1645
|
-
if (typeof input !== "number" || typeof output !== "number") return void 0;
|
|
1646
|
-
const reasoning = tokens["reasoning"];
|
|
1647
|
-
const cache = tokens["cache"];
|
|
1648
|
-
const cacheObj = cache && typeof cache === "object" && cache !== null ? cache : void 0;
|
|
1649
|
-
return {
|
|
1650
|
-
input,
|
|
1651
|
-
output,
|
|
1652
|
-
reasoning: typeof reasoning === "number" ? reasoning : 0,
|
|
1653
|
-
cacheRead: typeof (cacheObj == null ? void 0 : cacheObj["read"]) === "number" ? cacheObj["read"] : 0,
|
|
1654
|
-
cacheWrite: typeof (cacheObj == null ? void 0 : cacheObj["write"]) === "number" ? cacheObj["write"] : 0
|
|
1655
|
-
};
|
|
1656
|
-
}
|
|
1657
|
-
function modelSpendSpanAttributes(input) {
|
|
1658
|
-
const providerAttr = attrString("gen_ai.provider.name", canonicalModelProvider(input.providerID));
|
|
1659
|
-
const usageAttrs = buildRawModelUsageAttributes({
|
|
1660
|
-
inputTokens: input.tokens.input,
|
|
1661
|
-
outputTokens: input.tokens.output,
|
|
1662
|
-
reasoningTokens: input.tokens.reasoning,
|
|
1663
|
-
cacheReadInputTokens: input.tokens.cacheRead,
|
|
1664
|
-
cacheWriteInputTokens: input.tokens.cacheWrite
|
|
1665
|
-
});
|
|
1666
|
-
return providerAttr ? [providerAttr, ...usageAttrs] : usageAttrs;
|
|
1667
|
-
}
|
|
1668
|
-
function buildLlmGenAiAttributes(input) {
|
|
1669
|
-
const parsed = parseOpenCodeAssistantTokens(input.tokens);
|
|
1670
|
-
if (!parsed) return [];
|
|
1671
|
-
return [
|
|
1672
|
-
// OpenCode has already normalized both token axes. Provenance lets Dawn
|
|
1673
|
-
// select that contract without the SDK or customer declaring semantics.
|
|
1674
|
-
attrString(MODEL_USAGE_ATTRIBUTES.usageSource, "opencode"),
|
|
1675
|
-
attrString("gen_ai.system", input.providerID),
|
|
1676
|
-
attrString("gen_ai.request.model", input.modelID),
|
|
1677
|
-
attrString("gen_ai.response.model", input.modelID),
|
|
1678
|
-
...modelSpendSpanAttributes({ providerID: input.providerID, tokens: parsed })
|
|
1679
|
-
].filter((attribute) => attribute !== void 0);
|
|
1680
|
-
}
|
|
1681
|
-
|
|
1682
20
|
// src/tracing-linkage-helpers.ts
|
|
1683
21
|
function createSessionParentMapHelpers({
|
|
1684
22
|
sessions: sessions2,
|
|
@@ -1919,7 +257,7 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
|
|
|
1919
257
|
const state = sessions.get(partSessionID);
|
|
1920
258
|
if (!state) return;
|
|
1921
259
|
if (partObj["type"] === "text" && typeof partObj["text"] === "string") {
|
|
1922
|
-
state.outputParts.set(messageId,
|
|
260
|
+
state.outputParts.set(messageId, capText(partObj["text"]));
|
|
1923
261
|
} else if (partObj["type"] === "tool" && messageId) {
|
|
1924
262
|
const tool = partObj["tool"];
|
|
1925
263
|
const callID = partObj["callID"];
|
|
@@ -1949,7 +287,7 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
|
|
|
1949
287
|
if (typeof errorMessage !== "string" || errorMessage === "")
|
|
1950
288
|
throw new Error("message.part.updated: error tool state must have error string");
|
|
1951
289
|
const input = toolState["input"];
|
|
1952
|
-
const toolCallArgs = input === void 0 ? void 0 : typeof input === "string" ?
|
|
290
|
+
const toolCallArgs = input === void 0 ? void 0 : typeof input === "string" ? capText(input) : boundedStringify(input);
|
|
1953
291
|
if (endPendingToolSpanWithError(
|
|
1954
292
|
state,
|
|
1955
293
|
callID,
|
|
@@ -1962,7 +300,7 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
|
|
|
1962
300
|
}
|
|
1963
301
|
}
|
|
1964
302
|
} else if (partObj["type"] === "reasoning" && typeof partObj["text"] === "string") {
|
|
1965
|
-
state.reasoningParts.set(messageId,
|
|
303
|
+
state.reasoningParts.set(messageId, capText(partObj["text"]));
|
|
1966
304
|
}
|
|
1967
305
|
} else if (event.type === "message.updated") {
|
|
1968
306
|
const msgInfo = props["info"];
|
|
@@ -2049,7 +387,7 @@ type: ${name != null ? name : "UnknownError"}`;
|
|
|
2049
387
|
return;
|
|
2050
388
|
}
|
|
2051
389
|
if (outputText === void 0) return;
|
|
2052
|
-
const turnOutputText =
|
|
390
|
+
const turnOutputText = capText(state.turnOutputParts.join("\n\n"));
|
|
2053
391
|
const rootSpan = state.currentRootSpan;
|
|
2054
392
|
const isCompaction = state.isCompacting === true;
|
|
2055
393
|
if (state.parentId) {
|
|
@@ -2273,7 +611,7 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
2273
611
|
...promptMeta,
|
|
2274
612
|
properties: baseMeta.properties || promptMeta.properties ? { ...(_a = baseMeta.properties) != null ? _a : {}, ...(_b = promptMeta.properties) != null ? _b : {} } : void 0
|
|
2275
613
|
};
|
|
2276
|
-
const userText =
|
|
614
|
+
const userText = capText(textParts.join("\n"));
|
|
2277
615
|
state.currentInput = userText;
|
|
2278
616
|
state.turnOutputParts = [];
|
|
2279
617
|
state.turnOutputTokens = 0;
|
|
@@ -2400,7 +738,7 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
2400
738
|
if (!state || !state.currentEventId) return;
|
|
2401
739
|
if (args === void 0 || args === null)
|
|
2402
740
|
throw new Error("tool.execute.after: args is required");
|
|
2403
|
-
const toolCallArgs = typeof args === "string" ?
|
|
741
|
+
const toolCallArgs = typeof args === "string" ? capText(args) : boundedStringify(args);
|
|
2404
742
|
const startInfo = state.toolSpanStarts.get(callID);
|
|
2405
743
|
state.toolSpanStarts.delete(callID);
|
|
2406
744
|
const resultMetadata = result.metadata;
|
|
@@ -2480,9 +818,9 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
2480
818
|
sessions.set(sessionID, state);
|
|
2481
819
|
}
|
|
2482
820
|
const MAX_SYSTEM_PROMPT_LENGTH = 32768;
|
|
2483
|
-
const
|
|
821
|
+
const TRUNCATION_MARKER = "\n...[truncated]";
|
|
2484
822
|
const joined = output.system.join("\n\n");
|
|
2485
|
-
state.currentSystemPrompt = joined.length > MAX_SYSTEM_PROMPT_LENGTH ? joined.slice(0, MAX_SYSTEM_PROMPT_LENGTH -
|
|
823
|
+
state.currentSystemPrompt = joined.length > MAX_SYSTEM_PROMPT_LENGTH ? joined.slice(0, MAX_SYSTEM_PROMPT_LENGTH - TRUNCATION_MARKER.length) + TRUNCATION_MARKER : joined;
|
|
2486
824
|
} catch (err) {
|
|
2487
825
|
rateLimitedErrorLog(
|
|
2488
826
|
"experimental.chat.system.transform",
|
|
@@ -2494,7 +832,7 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
2494
832
|
}
|
|
2495
833
|
|
|
2496
834
|
// src/index.ts
|
|
2497
|
-
function createRaindropHandle(eventShipper, traceShipper) {
|
|
835
|
+
function createRaindropHandle(eventShipper, traceShipper, appGitContext) {
|
|
2498
836
|
return {
|
|
2499
837
|
users: {
|
|
2500
838
|
async identify(users) {
|
|
@@ -2505,12 +843,19 @@ function createRaindropHandle(eventShipper, traceShipper) {
|
|
|
2505
843
|
await Promise.all([eventShipper.flush(), traceShipper.flush()]);
|
|
2506
844
|
},
|
|
2507
845
|
async shutdown() {
|
|
2508
|
-
|
|
846
|
+
try {
|
|
847
|
+
await Promise.all([eventShipper.shutdown(), traceShipper.shutdown()]);
|
|
848
|
+
} finally {
|
|
849
|
+
try {
|
|
850
|
+
appGitContext.dispose();
|
|
851
|
+
} catch (e) {
|
|
852
|
+
}
|
|
853
|
+
}
|
|
2509
854
|
}
|
|
2510
855
|
};
|
|
2511
856
|
}
|
|
2512
|
-
async function plugin(input) {
|
|
2513
|
-
var _a;
|
|
857
|
+
async function plugin(input, opts = {}) {
|
|
858
|
+
var _a, _b, _c;
|
|
2514
859
|
const config = loadConfig(input.directory);
|
|
2515
860
|
function appLog(level, message) {
|
|
2516
861
|
console.log(`[raindrop-ai/opencode-plugin] [${level}] ${message}`);
|
|
@@ -2532,21 +877,28 @@ async function plugin(input) {
|
|
|
2532
877
|
].filter(Boolean);
|
|
2533
878
|
appLog("info", `Raindrop tracing enabled \u2014 destinations: ${destinations.join(", ")}`);
|
|
2534
879
|
}
|
|
2535
|
-
const
|
|
880
|
+
const configuredAppGit = (_a = opts.appGit) != null ? _a : config.appGit;
|
|
881
|
+
const appGitContext = createAppGitContext(
|
|
882
|
+
((_b = config.writeKey) == null ? void 0 : _b.trim()) || hasLocalDestination ? configuredAppGit : false,
|
|
883
|
+
{ allowLocalGit: false }
|
|
884
|
+
);
|
|
885
|
+
const eventShipper = new EventShipper({
|
|
2536
886
|
writeKey: config.writeKey,
|
|
2537
887
|
endpoint: config.endpoint,
|
|
2538
888
|
debug: config.debug,
|
|
2539
889
|
projectId: config.projectId,
|
|
2540
|
-
localDebuggerUrl: config.localWorkshopUrl
|
|
890
|
+
localDebuggerUrl: config.localWorkshopUrl,
|
|
891
|
+
appGitContext
|
|
2541
892
|
});
|
|
2542
|
-
const traceShipper = new
|
|
893
|
+
const traceShipper = new TraceShipper({
|
|
2543
894
|
writeKey: config.writeKey,
|
|
2544
895
|
endpoint: config.endpoint,
|
|
2545
896
|
debug: config.debug,
|
|
2546
897
|
projectId: config.projectId,
|
|
2547
|
-
localDebuggerUrl: config.localWorkshopUrl
|
|
898
|
+
localDebuggerUrl: config.localWorkshopUrl,
|
|
899
|
+
appGitContext
|
|
2548
900
|
});
|
|
2549
|
-
const worktree = (
|
|
901
|
+
const worktree = (_c = input.worktree) != null ? _c : input.directory;
|
|
2550
902
|
const hooks = createHooks(
|
|
2551
903
|
config,
|
|
2552
904
|
worktree,
|
|
@@ -2554,7 +906,7 @@ async function plugin(input) {
|
|
|
2554
906
|
eventShipper,
|
|
2555
907
|
traceShipper
|
|
2556
908
|
);
|
|
2557
|
-
return { ...hooks, raindrop: createRaindropHandle(eventShipper, traceShipper) };
|
|
909
|
+
return { ...hooks, raindrop: createRaindropHandle(eventShipper, traceShipper, appGitContext) };
|
|
2558
910
|
}
|
|
2559
911
|
export {
|
|
2560
912
|
plugin as default
|