@raindrop-ai/opencode-plugin 0.0.4 → 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/dist/index.cjs +380 -226
- package/dist/index.js +380 -226
- package/package.json +6 -2
package/dist/index.cjs
CHANGED
|
@@ -53,86 +53,30 @@ function loadConfig(projectDirectory) {
|
|
|
53
53
|
};
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
description: "Raindrop observability plugin for OpenCode \u2014 automatic session/event/span tracing",
|
|
61
|
-
type: "module",
|
|
62
|
-
main: "dist/index.cjs",
|
|
63
|
-
module: "dist/index.js",
|
|
64
|
-
types: "dist/index.d.ts",
|
|
65
|
-
exports: {
|
|
66
|
-
".": {
|
|
67
|
-
types: "./dist/index.d.ts",
|
|
68
|
-
import: "./dist/index.js",
|
|
69
|
-
require: "./dist/index.cjs"
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
sideEffects: false,
|
|
73
|
-
files: [
|
|
74
|
-
"dist/**"
|
|
75
|
-
],
|
|
76
|
-
scripts: {
|
|
77
|
-
build: "tsup",
|
|
78
|
-
dev: "tsup --watch",
|
|
79
|
-
clean: "rm -rf dist",
|
|
80
|
-
test: "cd tests && yarn test"
|
|
81
|
-
},
|
|
82
|
-
peerDependencies: {
|
|
83
|
-
"@opencode-ai/plugin": ">=1.2.0",
|
|
84
|
-
"@opencode-ai/sdk": ">=1.2.0"
|
|
85
|
-
},
|
|
86
|
-
peerDependenciesMeta: {
|
|
87
|
-
"@opencode-ai/sdk": {
|
|
88
|
-
optional: true
|
|
89
|
-
}
|
|
90
|
-
},
|
|
91
|
-
devDependencies: {
|
|
92
|
-
"@opencode-ai/plugin": "^1.2.0",
|
|
93
|
-
"@opencode-ai/sdk": "^1.2.0",
|
|
94
|
-
"@types/node": "^20.11.17",
|
|
95
|
-
tsup: "^8.4.0",
|
|
96
|
-
typescript: "^5.3.3"
|
|
97
|
-
},
|
|
98
|
-
tsup: {
|
|
99
|
-
entry: [
|
|
100
|
-
"src/index.ts"
|
|
101
|
-
],
|
|
102
|
-
format: [
|
|
103
|
-
"cjs",
|
|
104
|
-
"esm"
|
|
105
|
-
],
|
|
106
|
-
dts: {
|
|
107
|
-
resolve: true
|
|
108
|
-
},
|
|
109
|
-
clean: true
|
|
110
|
-
},
|
|
111
|
-
publishConfig: {
|
|
112
|
-
access: "public"
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
// src/shipper.ts
|
|
117
|
-
var PLUGIN_VERSION = package_default.version;
|
|
118
|
-
function getCryptoObj() {
|
|
119
|
-
return globalThis.crypto;
|
|
56
|
+
// ../core/dist/chunk-FTBZHS25.js
|
|
57
|
+
function getCrypto() {
|
|
58
|
+
const c = globalThis.crypto;
|
|
59
|
+
return c;
|
|
120
60
|
}
|
|
121
61
|
function randomBytes(length) {
|
|
122
|
-
const
|
|
62
|
+
const cryptoObj = getCrypto();
|
|
123
63
|
const out = new Uint8Array(length);
|
|
124
|
-
if (
|
|
125
|
-
|
|
64
|
+
if (cryptoObj && typeof cryptoObj.getRandomValues === "function") {
|
|
65
|
+
cryptoObj.getRandomValues(out);
|
|
126
66
|
return out;
|
|
127
67
|
}
|
|
128
68
|
for (let i = 0; i < out.length; i++) out[i] = Math.floor(Math.random() * 256);
|
|
129
69
|
return out;
|
|
130
70
|
}
|
|
131
71
|
function base64Encode(bytes) {
|
|
132
|
-
const
|
|
133
|
-
if (
|
|
72
|
+
const maybeBuffer = globalThis.Buffer;
|
|
73
|
+
if (maybeBuffer) {
|
|
74
|
+
return maybeBuffer.from(bytes).toString("base64");
|
|
75
|
+
}
|
|
134
76
|
let binary = "";
|
|
135
|
-
for (let i2 = 0; i2 < bytes.length; i2++)
|
|
77
|
+
for (let i2 = 0; i2 < bytes.length; i2++) {
|
|
78
|
+
binary += String.fromCharCode(bytes[i2]);
|
|
79
|
+
}
|
|
136
80
|
const btoaFn = globalThis.btoa;
|
|
137
81
|
if (typeof btoaFn === "function") return btoaFn(binary);
|
|
138
82
|
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
@@ -153,69 +97,9 @@ function base64Encode(bytes) {
|
|
|
153
97
|
}
|
|
154
98
|
return out;
|
|
155
99
|
}
|
|
156
|
-
var SERVICE_NAME = "raindrop.opencode-plugin";
|
|
157
|
-
var SpanStatusCode = {
|
|
158
|
-
ERROR: 2
|
|
159
|
-
};
|
|
160
100
|
function generateId() {
|
|
161
101
|
return base64Encode(randomBytes(12)).replace(/[+/=]/g, "").slice(0, 16);
|
|
162
102
|
}
|
|
163
|
-
function createSpanIds(parent) {
|
|
164
|
-
const traceId = parent ? parent.traceIdB64 : base64Encode(randomBytes(16));
|
|
165
|
-
const spanId = base64Encode(randomBytes(8));
|
|
166
|
-
return {
|
|
167
|
-
traceIdB64: traceId,
|
|
168
|
-
spanIdB64: spanId,
|
|
169
|
-
parentSpanIdB64: parent ? parent.spanIdB64 : void 0
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
function nowUnixNanoString() {
|
|
173
|
-
return Date.now().toString() + "000000";
|
|
174
|
-
}
|
|
175
|
-
function attrString(key, value) {
|
|
176
|
-
if (value === void 0) return void 0;
|
|
177
|
-
return { key, value: { stringValue: value } };
|
|
178
|
-
}
|
|
179
|
-
function attrInt(key, value) {
|
|
180
|
-
if (value === void 0) return void 0;
|
|
181
|
-
if (!Number.isFinite(value)) return void 0;
|
|
182
|
-
return { key, value: { intValue: String(Math.trunc(value)) } };
|
|
183
|
-
}
|
|
184
|
-
function buildOtlpSpan(args) {
|
|
185
|
-
const attrs = [
|
|
186
|
-
attrString("span.id", args.ids.spanIdB64),
|
|
187
|
-
attrString("span.parent.id", args.ids.parentSpanIdB64),
|
|
188
|
-
...args.attributes
|
|
189
|
-
].filter((x) => x !== void 0);
|
|
190
|
-
const span = {
|
|
191
|
-
traceId: args.ids.traceIdB64,
|
|
192
|
-
spanId: args.ids.spanIdB64,
|
|
193
|
-
name: args.name,
|
|
194
|
-
startTimeUnixNano: args.startTimeUnixNano,
|
|
195
|
-
endTimeUnixNano: args.endTimeUnixNano
|
|
196
|
-
};
|
|
197
|
-
if (args.ids.parentSpanIdB64) span.parentSpanId = args.ids.parentSpanIdB64;
|
|
198
|
-
if (attrs.length) span.attributes = attrs;
|
|
199
|
-
if (args.status) span.status = args.status;
|
|
200
|
-
return span;
|
|
201
|
-
}
|
|
202
|
-
function buildExportTraceServiceRequest(spans) {
|
|
203
|
-
return {
|
|
204
|
-
resourceSpans: [
|
|
205
|
-
{
|
|
206
|
-
resource: {
|
|
207
|
-
attributes: [{ key: "service.name", value: { stringValue: SERVICE_NAME } }]
|
|
208
|
-
},
|
|
209
|
-
scopeSpans: [
|
|
210
|
-
{
|
|
211
|
-
scope: { name: SERVICE_NAME, version: PLUGIN_VERSION },
|
|
212
|
-
spans
|
|
213
|
-
}
|
|
214
|
-
]
|
|
215
|
-
}
|
|
216
|
-
]
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
103
|
function wait(ms) {
|
|
220
104
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
221
105
|
}
|
|
@@ -228,7 +112,7 @@ function parseRetryAfter(headers) {
|
|
|
228
112
|
const value = (_a = headers.get("Retry-After")) != null ? _a : headers.get("retry-after");
|
|
229
113
|
if (!value) return void 0;
|
|
230
114
|
const asNumber = Number(value);
|
|
231
|
-
if (!Number.isNaN(asNumber)) return asNumber * 1e3;
|
|
115
|
+
if (value.trim() !== "" && !Number.isNaN(asNumber)) return asNumber * 1e3;
|
|
232
116
|
const asDate = new Date(value).getTime();
|
|
233
117
|
if (!Number.isNaN(asDate)) {
|
|
234
118
|
const delta = asDate - Date.now();
|
|
@@ -237,26 +121,29 @@ function parseRetryAfter(headers) {
|
|
|
237
121
|
return void 0;
|
|
238
122
|
}
|
|
239
123
|
function getRetryDelayMs(attemptNumber, previousError) {
|
|
240
|
-
if (previousError
|
|
124
|
+
if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
|
|
241
125
|
const v = previousError.retryAfterMs;
|
|
242
126
|
if (typeof v === "number") return Math.max(0, v);
|
|
243
127
|
}
|
|
244
128
|
if (attemptNumber <= 1) return 0;
|
|
245
|
-
|
|
129
|
+
const base = 500;
|
|
130
|
+
const factor = Math.pow(2, attemptNumber - 2);
|
|
131
|
+
return base * factor;
|
|
246
132
|
}
|
|
247
133
|
async function withRetry(operation, opName, opts) {
|
|
134
|
+
const prefix = opts.sdkName ? `[raindrop-ai/${opts.sdkName}]` : "[raindrop-ai/core]";
|
|
248
135
|
let lastError = void 0;
|
|
249
|
-
for (let
|
|
250
|
-
if (
|
|
251
|
-
const delay = getRetryDelayMs(
|
|
136
|
+
for (let attemptNumber = 1; attemptNumber <= opts.maxAttempts; attemptNumber++) {
|
|
137
|
+
if (attemptNumber > 1) {
|
|
138
|
+
const delay = getRetryDelayMs(attemptNumber, lastError);
|
|
252
139
|
if (opts.debug) {
|
|
253
140
|
console.warn(
|
|
254
|
-
|
|
141
|
+
`${prefix} ${opName} retry ${attemptNumber}/${opts.maxAttempts} in ${delay}ms`
|
|
255
142
|
);
|
|
256
143
|
}
|
|
257
144
|
if (delay > 0) await wait(delay);
|
|
258
145
|
} else if (opts.debug) {
|
|
259
|
-
console.log(
|
|
146
|
+
console.log(`${prefix} ${opName} attempt ${attemptNumber}/${opts.maxAttempts}`);
|
|
260
147
|
}
|
|
261
148
|
try {
|
|
262
149
|
return await operation();
|
|
@@ -265,20 +152,26 @@ async function withRetry(operation, opName, opts) {
|
|
|
265
152
|
if (opts.debug) {
|
|
266
153
|
const msg = err instanceof Error ? err.message : String(err);
|
|
267
154
|
console.warn(
|
|
268
|
-
|
|
155
|
+
`${prefix} ${opName} attempt ${attemptNumber} failed: ${msg}${attemptNumber === opts.maxAttempts ? " (no more retries)" : ""}`
|
|
269
156
|
);
|
|
270
157
|
}
|
|
271
|
-
if (
|
|
158
|
+
if (lastError && typeof lastError === "object" && "retryable" in lastError && !lastError.retryable)
|
|
159
|
+
break;
|
|
160
|
+
if (attemptNumber === opts.maxAttempts) break;
|
|
272
161
|
}
|
|
273
162
|
}
|
|
274
163
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
275
164
|
}
|
|
276
165
|
async function postJson(url, body, headers, opts) {
|
|
166
|
+
const opName = `POST ${url}`;
|
|
277
167
|
await withRetry(
|
|
278
168
|
async () => {
|
|
279
169
|
const resp = await fetch(url, {
|
|
280
170
|
method: "POST",
|
|
281
|
-
headers: {
|
|
171
|
+
headers: {
|
|
172
|
+
"Content-Type": "application/json",
|
|
173
|
+
...headers
|
|
174
|
+
},
|
|
282
175
|
body: JSON.stringify(body)
|
|
283
176
|
});
|
|
284
177
|
if (!resp.ok) {
|
|
@@ -288,13 +181,71 @@ async function postJson(url, body, headers, opts) {
|
|
|
288
181
|
);
|
|
289
182
|
const retryAfterMs = parseRetryAfter(resp.headers);
|
|
290
183
|
if (typeof retryAfterMs === "number") err.retryAfterMs = retryAfterMs;
|
|
184
|
+
err.retryable = resp.status === 429 || resp.status >= 500;
|
|
291
185
|
throw err;
|
|
292
186
|
}
|
|
293
187
|
},
|
|
294
|
-
|
|
188
|
+
opName,
|
|
295
189
|
opts
|
|
296
190
|
);
|
|
297
191
|
}
|
|
192
|
+
var SpanStatusCode = {
|
|
193
|
+
UNSET: 0,
|
|
194
|
+
OK: 1,
|
|
195
|
+
ERROR: 2
|
|
196
|
+
};
|
|
197
|
+
function createSpanIds(parent) {
|
|
198
|
+
const traceId = parent ? parent.traceIdB64 : base64Encode(randomBytes(16));
|
|
199
|
+
const spanId = base64Encode(randomBytes(8));
|
|
200
|
+
return {
|
|
201
|
+
traceIdB64: traceId,
|
|
202
|
+
spanIdB64: spanId,
|
|
203
|
+
parentSpanIdB64: parent ? parent.spanIdB64 : void 0
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function nowUnixNanoString() {
|
|
207
|
+
return Date.now().toString() + "000000";
|
|
208
|
+
}
|
|
209
|
+
function attrString(key, value) {
|
|
210
|
+
if (value === void 0) return void 0;
|
|
211
|
+
return { key, value: { stringValue: value } };
|
|
212
|
+
}
|
|
213
|
+
function attrInt(key, value) {
|
|
214
|
+
if (value === void 0) return void 0;
|
|
215
|
+
if (!Number.isFinite(value)) return void 0;
|
|
216
|
+
return { key, value: { intValue: String(Math.trunc(value)) } };
|
|
217
|
+
}
|
|
218
|
+
function buildOtlpSpan(args) {
|
|
219
|
+
const attrs = args.attributes.filter((x) => x !== void 0);
|
|
220
|
+
const span = {
|
|
221
|
+
traceId: args.ids.traceIdB64,
|
|
222
|
+
spanId: args.ids.spanIdB64,
|
|
223
|
+
name: args.name,
|
|
224
|
+
startTimeUnixNano: args.startTimeUnixNano,
|
|
225
|
+
endTimeUnixNano: args.endTimeUnixNano
|
|
226
|
+
};
|
|
227
|
+
if (args.ids.parentSpanIdB64) span.parentSpanId = args.ids.parentSpanIdB64;
|
|
228
|
+
if (attrs.length) span.attributes = attrs;
|
|
229
|
+
if (args.status) span.status = args.status;
|
|
230
|
+
return span;
|
|
231
|
+
}
|
|
232
|
+
function buildExportTraceServiceRequest(spans, serviceName = "raindrop.core", serviceVersion = "0.0.0") {
|
|
233
|
+
return {
|
|
234
|
+
resourceSpans: [
|
|
235
|
+
{
|
|
236
|
+
resource: {
|
|
237
|
+
attributes: [{ key: "service.name", value: { stringValue: serviceName } }]
|
|
238
|
+
},
|
|
239
|
+
scopeSpans: [
|
|
240
|
+
{
|
|
241
|
+
scope: { name: serviceName, version: serviceVersion },
|
|
242
|
+
spans
|
|
243
|
+
}
|
|
244
|
+
]
|
|
245
|
+
}
|
|
246
|
+
]
|
|
247
|
+
};
|
|
248
|
+
}
|
|
298
249
|
function mergePatches(target, source) {
|
|
299
250
|
var _a, _b, _c, _d;
|
|
300
251
|
const out = { ...target, ...source };
|
|
@@ -312,34 +263,58 @@ var EventShipper = class {
|
|
|
312
263
|
this.sticky = /* @__PURE__ */ new Map();
|
|
313
264
|
this.timers = /* @__PURE__ */ new Map();
|
|
314
265
|
this.inFlight = /* @__PURE__ */ new Set();
|
|
315
|
-
var _a, _b;
|
|
316
|
-
this.writeKey = opts.writeKey;
|
|
317
|
-
this.baseUrl = (
|
|
266
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
267
|
+
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
268
|
+
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
269
|
+
this.enabled = opts.enabled !== false;
|
|
318
270
|
this.debug = opts.debug;
|
|
319
|
-
this.partialFlushMs = (
|
|
271
|
+
this.partialFlushMs = (_c = opts.partialFlushMs) != null ? _c : 1e3;
|
|
272
|
+
this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
|
|
273
|
+
this.prefix = `[raindrop-ai/${this.sdkName}]`;
|
|
274
|
+
this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
|
|
275
|
+
const isNode = typeof process !== "undefined" && typeof process.version === "string";
|
|
276
|
+
this.context = {
|
|
277
|
+
library: {
|
|
278
|
+
name: (_f = opts.libraryName) != null ? _f : "@raindrop-ai/core",
|
|
279
|
+
version: (_g = opts.libraryVersion) != null ? _g : "0.0.0"
|
|
280
|
+
},
|
|
281
|
+
metadata: {
|
|
282
|
+
jsRuntime: isNode ? "node" : "web",
|
|
283
|
+
...isNode ? { nodeVersion: process.version } : {}
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
isDebugEnabled() {
|
|
288
|
+
return this.debug;
|
|
289
|
+
}
|
|
290
|
+
authHeaders() {
|
|
291
|
+
return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
|
|
320
292
|
}
|
|
321
293
|
async patch(eventId, patch) {
|
|
322
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
323
|
-
if (!
|
|
294
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
295
|
+
if (!this.enabled) return;
|
|
296
|
+
if (!eventId || !eventId.trim()) return;
|
|
324
297
|
if (this.debug) {
|
|
325
|
-
console.log(
|
|
298
|
+
console.log(`${this.prefix} queue patch`, {
|
|
326
299
|
eventId,
|
|
327
300
|
userId: patch.userId,
|
|
301
|
+
eventName: patch.eventName,
|
|
328
302
|
hasInput: typeof patch.input === "string" && patch.input.length > 0,
|
|
329
303
|
hasOutput: typeof patch.output === "string" && patch.output.length > 0,
|
|
304
|
+
attachments: (_b = (_a = patch.attachments) == null ? void 0 : _a.length) != null ? _b : 0,
|
|
330
305
|
isPending: patch.isPending
|
|
331
306
|
});
|
|
332
307
|
}
|
|
333
|
-
const sticky = (
|
|
334
|
-
const existing = (
|
|
308
|
+
const sticky = (_c = this.sticky.get(eventId)) != null ? _c : {};
|
|
309
|
+
const existing = (_d = this.buffers.get(eventId)) != null ? _d : {};
|
|
335
310
|
const merged = mergePatches(existing, patch);
|
|
336
|
-
merged.isPending = (
|
|
311
|
+
merged.isPending = (_g = (_f = (_e = patch.isPending) != null ? _e : existing.isPending) != null ? _f : sticky.isPending) != null ? _g : true;
|
|
337
312
|
this.buffers.set(eventId, merged);
|
|
338
313
|
this.sticky.set(eventId, {
|
|
339
|
-
userId: (
|
|
340
|
-
convoId: (
|
|
341
|
-
eventName: (
|
|
342
|
-
isPending: (
|
|
314
|
+
userId: (_h = merged.userId) != null ? _h : sticky.userId,
|
|
315
|
+
convoId: (_i = merged.convoId) != null ? _i : sticky.convoId,
|
|
316
|
+
eventName: (_j = merged.eventName) != null ? _j : sticky.eventName,
|
|
317
|
+
isPending: (_k = merged.isPending) != null ? _k : sticky.isPending
|
|
343
318
|
});
|
|
344
319
|
const t = this.timers.get(eventId);
|
|
345
320
|
if (t) clearTimeout(t);
|
|
@@ -357,6 +332,7 @@ var EventShipper = class {
|
|
|
357
332
|
await this.patch(eventId, { ...patch, isPending: false });
|
|
358
333
|
}
|
|
359
334
|
async flush() {
|
|
335
|
+
if (!this.enabled) return;
|
|
360
336
|
const ids = [...this.buffers.keys()];
|
|
361
337
|
await Promise.all(ids.map((id) => this.flushOne(id)));
|
|
362
338
|
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
@@ -367,8 +343,70 @@ var EventShipper = class {
|
|
|
367
343
|
this.timers.clear();
|
|
368
344
|
await this.flush();
|
|
369
345
|
}
|
|
346
|
+
async trackSignal(signal) {
|
|
347
|
+
var _a, _b;
|
|
348
|
+
if (!this.enabled) return;
|
|
349
|
+
const body = [
|
|
350
|
+
{
|
|
351
|
+
event_id: signal.eventId,
|
|
352
|
+
signal_name: signal.name,
|
|
353
|
+
signal_type: (_a = signal.type) != null ? _a : "default",
|
|
354
|
+
timestamp: signal.timestamp,
|
|
355
|
+
sentiment: signal.sentiment,
|
|
356
|
+
attachment_id: signal.attachmentId,
|
|
357
|
+
properties: {
|
|
358
|
+
...(_b = signal.properties) != null ? _b : {},
|
|
359
|
+
...signal.comment ? { comment: signal.comment } : {},
|
|
360
|
+
...signal.after ? { after: signal.after } : {}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
];
|
|
364
|
+
const url = `${this.baseUrl}signals/track`;
|
|
365
|
+
try {
|
|
366
|
+
await postJson(url, body, this.authHeaders(), {
|
|
367
|
+
maxAttempts: 3,
|
|
368
|
+
debug: this.debug,
|
|
369
|
+
sdkName: this.sdkName
|
|
370
|
+
});
|
|
371
|
+
} catch (err) {
|
|
372
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
373
|
+
console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
async identify(users) {
|
|
377
|
+
if (!this.enabled) return;
|
|
378
|
+
const list = Array.isArray(users) ? users : [users];
|
|
379
|
+
const body = list.filter((user) => {
|
|
380
|
+
if (!(user == null ? void 0 : user.userId) || !user.userId.trim()) {
|
|
381
|
+
if (this.debug) {
|
|
382
|
+
console.warn(`${this.prefix} skipping identify: missing userId`);
|
|
383
|
+
}
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
return true;
|
|
387
|
+
}).map((user) => {
|
|
388
|
+
var _a;
|
|
389
|
+
return {
|
|
390
|
+
user_id: user.userId,
|
|
391
|
+
traits: (_a = user.traits) != null ? _a : {}
|
|
392
|
+
};
|
|
393
|
+
});
|
|
394
|
+
if (body.length === 0) return;
|
|
395
|
+
const url = `${this.baseUrl}users/identify`;
|
|
396
|
+
try {
|
|
397
|
+
await postJson(url, body, this.authHeaders(), {
|
|
398
|
+
maxAttempts: 3,
|
|
399
|
+
debug: this.debug,
|
|
400
|
+
sdkName: this.sdkName
|
|
401
|
+
});
|
|
402
|
+
} catch (err) {
|
|
403
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
404
|
+
console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
370
407
|
async flushOne(eventId) {
|
|
371
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
408
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
409
|
+
if (!this.enabled) return;
|
|
372
410
|
const timer = this.timers.get(eventId);
|
|
373
411
|
if (timer) {
|
|
374
412
|
clearTimeout(timer);
|
|
@@ -378,105 +416,123 @@ var EventShipper = class {
|
|
|
378
416
|
this.buffers.delete(eventId);
|
|
379
417
|
if (!accumulated) return;
|
|
380
418
|
const sticky = (_a = this.sticky.get(eventId)) != null ? _a : {};
|
|
381
|
-
const eventName = (_c = (_b = accumulated.eventName) != null ? _b : sticky.eventName) != null ? _c :
|
|
419
|
+
const eventName = (_c = (_b = accumulated.eventName) != null ? _b : sticky.eventName) != null ? _c : this.defaultEventName;
|
|
382
420
|
const userId = (_d = accumulated.userId) != null ? _d : sticky.userId;
|
|
383
421
|
if (!userId) {
|
|
384
422
|
if (this.debug) {
|
|
385
|
-
console.warn(
|
|
386
|
-
`[raindrop-ai/opencode-plugin] skipping track_partial for ${eventId}: missing userId`
|
|
387
|
-
);
|
|
423
|
+
console.warn(`${this.prefix} skipping track_partial for ${eventId}: missing userId`);
|
|
388
424
|
}
|
|
425
|
+
this.sticky.delete(eventId);
|
|
389
426
|
return;
|
|
390
427
|
}
|
|
428
|
+
const { wizardSession, ...restProperties } = (_e = accumulated.properties) != null ? _e : {};
|
|
391
429
|
const payload = {
|
|
392
430
|
event_id: eventId,
|
|
393
431
|
user_id: userId,
|
|
394
432
|
event: eventName,
|
|
395
|
-
timestamp: (
|
|
433
|
+
timestamp: (_f = accumulated.timestamp) != null ? _f : (/* @__PURE__ */ new Date()).toISOString(),
|
|
396
434
|
ai_data: {
|
|
397
435
|
input: accumulated.input,
|
|
398
436
|
output: accumulated.output,
|
|
399
437
|
model: accumulated.model,
|
|
400
|
-
convo_id: (
|
|
438
|
+
convo_id: (_g = accumulated.convoId) != null ? _g : sticky.convoId
|
|
401
439
|
},
|
|
402
440
|
properties: {
|
|
403
|
-
...
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
metadata: { jsRuntime: "node" }
|
|
407
|
-
}
|
|
441
|
+
...restProperties,
|
|
442
|
+
...wizardSession ? { "raindrop.wizardSession": wizardSession } : {},
|
|
443
|
+
$context: this.context
|
|
408
444
|
},
|
|
409
445
|
attachments: accumulated.attachments,
|
|
410
446
|
is_pending: ((_i = (_h = accumulated.isPending) != null ? _h : sticky.isPending) != null ? _i : true) !== false
|
|
411
447
|
};
|
|
412
448
|
const url = `${this.baseUrl}events/track_partial`;
|
|
413
449
|
if (this.debug) {
|
|
414
|
-
console.log(
|
|
450
|
+
console.log(`${this.prefix} sending track_partial`, {
|
|
415
451
|
eventId,
|
|
416
452
|
eventName,
|
|
417
453
|
userId,
|
|
418
|
-
isPending: payload
|
|
454
|
+
isPending: payload.is_pending,
|
|
419
455
|
inputPreview: typeof accumulated.input === "string" ? accumulated.input.slice(0, 120) : void 0,
|
|
420
456
|
outputPreview: typeof accumulated.output === "string" ? accumulated.output.slice(0, 120) : void 0,
|
|
457
|
+
attachments: (_k = (_j = accumulated.attachments) == null ? void 0 : _j.length) != null ? _k : 0,
|
|
458
|
+
attachmentKinds: (_m = (_l = accumulated.attachments) == null ? void 0 : _l.map((a) => ({
|
|
459
|
+
type: a.type,
|
|
460
|
+
role: a.role,
|
|
461
|
+
name: a.name,
|
|
462
|
+
valuePreview: a.value.slice(0, 60)
|
|
463
|
+
}))) != null ? _m : [],
|
|
421
464
|
endpoint: url
|
|
422
465
|
});
|
|
423
466
|
}
|
|
424
|
-
const p = postJson(
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
);
|
|
467
|
+
const p = postJson(url, payload, this.authHeaders(), {
|
|
468
|
+
maxAttempts: 3,
|
|
469
|
+
debug: this.debug,
|
|
470
|
+
sdkName: this.sdkName
|
|
471
|
+
});
|
|
430
472
|
this.inFlight.add(p);
|
|
431
473
|
try {
|
|
432
474
|
try {
|
|
433
475
|
await p;
|
|
434
476
|
if (this.debug) {
|
|
435
|
-
console.log(
|
|
477
|
+
console.log(`${this.prefix} sent track_partial ${eventId} (${eventName})`);
|
|
436
478
|
}
|
|
437
479
|
} catch (err) {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
console.warn(
|
|
441
|
-
`[raindrop-ai/opencode-plugin] failed to send track_partial (dropping): ${msg}`
|
|
442
|
-
);
|
|
443
|
-
}
|
|
480
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
481
|
+
console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`);
|
|
444
482
|
}
|
|
445
483
|
} finally {
|
|
446
484
|
this.inFlight.delete(p);
|
|
447
485
|
}
|
|
448
|
-
|
|
486
|
+
const isPending = payload.is_pending;
|
|
487
|
+
if (!isPending) {
|
|
488
|
+
this.sticky.delete(eventId);
|
|
489
|
+
}
|
|
449
490
|
}
|
|
450
491
|
};
|
|
451
492
|
var TraceShipper = class {
|
|
452
493
|
constructor(opts) {
|
|
453
494
|
this.queue = [];
|
|
454
495
|
this.inFlight = /* @__PURE__ */ new Set();
|
|
455
|
-
var _a, _b;
|
|
456
|
-
this.writeKey = opts.writeKey;
|
|
457
|
-
this.baseUrl = (
|
|
496
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
497
|
+
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
498
|
+
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
499
|
+
this.enabled = opts.enabled !== false;
|
|
458
500
|
this.debug = opts.debug;
|
|
459
|
-
this.
|
|
460
|
-
this.
|
|
461
|
-
this.
|
|
501
|
+
this.debugSpans = opts.debugSpans === true;
|
|
502
|
+
this.flushIntervalMs = (_c = opts.flushIntervalMs) != null ? _c : 1e3;
|
|
503
|
+
this.maxBatchSize = (_d = opts.maxBatchSize) != null ? _d : 50;
|
|
504
|
+
this.maxQueueSize = (_e = opts.maxQueueSize) != null ? _e : 5e3;
|
|
505
|
+
this.sdkName = (_f = opts.sdkName) != null ? _f : "core";
|
|
506
|
+
this.prefix = `[raindrop-ai/${this.sdkName}]`;
|
|
507
|
+
this.serviceName = (_g = opts.serviceName) != null ? _g : "raindrop.core";
|
|
508
|
+
this.serviceVersion = (_h = opts.serviceVersion) != null ? _h : "0.0.0";
|
|
509
|
+
}
|
|
510
|
+
isDebugEnabled() {
|
|
511
|
+
return this.debug;
|
|
512
|
+
}
|
|
513
|
+
authHeaders() {
|
|
514
|
+
return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
|
|
462
515
|
}
|
|
463
516
|
startSpan(args) {
|
|
464
|
-
var _a;
|
|
517
|
+
var _a, _b;
|
|
465
518
|
const ids = createSpanIds(args.parent);
|
|
466
|
-
const started = nowUnixNanoString();
|
|
519
|
+
const started = (_a = args.startTimeUnixNano) != null ? _a : nowUnixNanoString();
|
|
467
520
|
const attrs = [
|
|
468
|
-
attrString("ai.telemetry.metadata.raindrop.eventId", args.eventId)
|
|
521
|
+
attrString("ai.telemetry.metadata.raindrop.eventId", args.eventId),
|
|
522
|
+
attrString("ai.operationId", args.operationId)
|
|
469
523
|
];
|
|
470
|
-
if ((
|
|
524
|
+
if ((_b = args.attributes) == null ? void 0 : _b.length) attrs.push(...args.attributes);
|
|
471
525
|
return { ids, name: args.name, startTimeUnixNano: started, attributes: attrs };
|
|
472
526
|
}
|
|
473
527
|
endSpan(span, extra) {
|
|
474
|
-
var _a;
|
|
528
|
+
var _a, _b;
|
|
475
529
|
if (span.endTimeUnixNano) return;
|
|
476
|
-
span.endTimeUnixNano = nowUnixNanoString();
|
|
477
|
-
if ((
|
|
478
|
-
|
|
479
|
-
|
|
530
|
+
span.endTimeUnixNano = (_a = extra == null ? void 0 : extra.endTimeUnixNano) != null ? _a : nowUnixNanoString();
|
|
531
|
+
if ((_b = extra == null ? void 0 : extra.attributes) == null ? void 0 : _b.length) {
|
|
532
|
+
span.attributes.push(...extra.attributes);
|
|
533
|
+
}
|
|
534
|
+
let status = extra == null ? void 0 : extra.status;
|
|
535
|
+
if (!status && (extra == null ? void 0 : extra.error) !== void 0) {
|
|
480
536
|
const message = extra.error instanceof Error ? extra.error.message : String(extra.error);
|
|
481
537
|
status = { code: SpanStatusCode.ERROR, message };
|
|
482
538
|
}
|
|
@@ -490,10 +546,6 @@ var TraceShipper = class {
|
|
|
490
546
|
});
|
|
491
547
|
this.enqueue(otlp);
|
|
492
548
|
}
|
|
493
|
-
/**
|
|
494
|
-
* Create and enqueue a span with both start and end times (atomic insert).
|
|
495
|
-
* Used for tool spans and any span whose full duration is known at write time.
|
|
496
|
-
*/
|
|
497
549
|
createSpan(args) {
|
|
498
550
|
var _a;
|
|
499
551
|
const ids = createSpanIds(args.parent);
|
|
@@ -512,7 +564,18 @@ var TraceShipper = class {
|
|
|
512
564
|
this.enqueue(otlp);
|
|
513
565
|
}
|
|
514
566
|
enqueue(span) {
|
|
515
|
-
if (this.
|
|
567
|
+
if (!this.enabled) return;
|
|
568
|
+
if (this.debugSpans) {
|
|
569
|
+
const short = (s) => s ? s.slice(-8) : "none";
|
|
570
|
+
console.log(
|
|
571
|
+
`${this.prefix}[span] name=${span.name} trace=${short(span.traceId)} span=${short(span.spanId)} parent=${short(
|
|
572
|
+
span.parentSpanId
|
|
573
|
+
)}`
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
if (this.queue.length >= this.maxQueueSize) {
|
|
577
|
+
this.queue.shift();
|
|
578
|
+
}
|
|
516
579
|
this.queue.push(span);
|
|
517
580
|
if (this.queue.length >= this.maxBatchSize) {
|
|
518
581
|
void this.flush().catch(() => {
|
|
@@ -528,42 +591,34 @@ var TraceShipper = class {
|
|
|
528
591
|
}
|
|
529
592
|
}
|
|
530
593
|
async flush() {
|
|
594
|
+
if (!this.enabled) return;
|
|
531
595
|
if (this.timer) {
|
|
532
596
|
clearTimeout(this.timer);
|
|
533
597
|
this.timer = void 0;
|
|
534
598
|
}
|
|
535
599
|
while (this.queue.length > 0) {
|
|
536
600
|
const batch = this.queue.splice(0, this.maxBatchSize);
|
|
537
|
-
const body = buildExportTraceServiceRequest(batch);
|
|
601
|
+
const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
|
|
538
602
|
const url = `${this.baseUrl}traces`;
|
|
539
|
-
const spanNames = batch.map((span) => span.name);
|
|
540
603
|
if (this.debug) {
|
|
541
|
-
console.log(
|
|
604
|
+
console.log(`${this.prefix} sending traces batch`, {
|
|
542
605
|
spans: batch.length,
|
|
543
|
-
endpoint: url
|
|
544
|
-
spanNames
|
|
606
|
+
endpoint: url
|
|
545
607
|
});
|
|
546
608
|
}
|
|
547
|
-
const p = postJson(
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
);
|
|
609
|
+
const p = postJson(url, body, this.authHeaders(), {
|
|
610
|
+
maxAttempts: 3,
|
|
611
|
+
debug: this.debug,
|
|
612
|
+
sdkName: this.sdkName
|
|
613
|
+
});
|
|
553
614
|
this.inFlight.add(p);
|
|
554
615
|
try {
|
|
555
616
|
try {
|
|
556
617
|
await p;
|
|
557
|
-
if (this.debug) {
|
|
558
|
-
console.log(`[raindrop-ai/opencode-plugin] sent ${batch.length} spans`, {
|
|
559
|
-
spanNames
|
|
560
|
-
});
|
|
561
|
-
}
|
|
618
|
+
if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
|
|
562
619
|
} catch (err) {
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
console.warn(`[raindrop-ai/opencode-plugin] failed to send spans (dropping): ${msg}`);
|
|
566
|
-
}
|
|
620
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
621
|
+
console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`);
|
|
567
622
|
}
|
|
568
623
|
} finally {
|
|
569
624
|
this.inFlight.delete(p);
|
|
@@ -581,6 +636,105 @@ var TraceShipper = class {
|
|
|
581
636
|
}
|
|
582
637
|
};
|
|
583
638
|
|
|
639
|
+
// package.json
|
|
640
|
+
var package_default = {
|
|
641
|
+
name: "@raindrop-ai/opencode-plugin",
|
|
642
|
+
version: "0.0.5",
|
|
643
|
+
description: "Raindrop observability plugin for OpenCode \u2014 automatic session/event/span tracing",
|
|
644
|
+
type: "module",
|
|
645
|
+
main: "dist/index.cjs",
|
|
646
|
+
module: "dist/index.js",
|
|
647
|
+
types: "dist/index.d.ts",
|
|
648
|
+
exports: {
|
|
649
|
+
".": {
|
|
650
|
+
types: "./dist/index.d.ts",
|
|
651
|
+
import: "./dist/index.js",
|
|
652
|
+
require: "./dist/index.cjs"
|
|
653
|
+
}
|
|
654
|
+
},
|
|
655
|
+
sideEffects: false,
|
|
656
|
+
files: [
|
|
657
|
+
"dist/**"
|
|
658
|
+
],
|
|
659
|
+
scripts: {
|
|
660
|
+
build: "tsup",
|
|
661
|
+
dev: "tsup --watch",
|
|
662
|
+
clean: "rm -rf dist",
|
|
663
|
+
test: "cd tests && yarn test"
|
|
664
|
+
},
|
|
665
|
+
peerDependencies: {
|
|
666
|
+
"@opencode-ai/plugin": ">=1.2.0",
|
|
667
|
+
"@opencode-ai/sdk": ">=1.2.0"
|
|
668
|
+
},
|
|
669
|
+
peerDependenciesMeta: {
|
|
670
|
+
"@opencode-ai/sdk": {
|
|
671
|
+
optional: true
|
|
672
|
+
}
|
|
673
|
+
},
|
|
674
|
+
devDependencies: {
|
|
675
|
+
"@raindrop-ai/core": "*",
|
|
676
|
+
"@opencode-ai/plugin": "^1.2.0",
|
|
677
|
+
"@opencode-ai/sdk": "^1.2.0",
|
|
678
|
+
"@types/node": "^20.11.17",
|
|
679
|
+
tsup: "^8.4.0",
|
|
680
|
+
typescript: "^5.3.3"
|
|
681
|
+
},
|
|
682
|
+
tsup: {
|
|
683
|
+
entry: [
|
|
684
|
+
"src/index.ts"
|
|
685
|
+
],
|
|
686
|
+
format: [
|
|
687
|
+
"cjs",
|
|
688
|
+
"esm"
|
|
689
|
+
],
|
|
690
|
+
dts: {
|
|
691
|
+
resolve: true
|
|
692
|
+
},
|
|
693
|
+
clean: true,
|
|
694
|
+
noExternal: [
|
|
695
|
+
"@raindrop-ai/core"
|
|
696
|
+
]
|
|
697
|
+
},
|
|
698
|
+
publishConfig: {
|
|
699
|
+
access: "public"
|
|
700
|
+
}
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
// src/shipper.ts
|
|
704
|
+
var EventShipper2 = class extends EventShipper {
|
|
705
|
+
constructor(opts) {
|
|
706
|
+
var _a, _b, _c, _d;
|
|
707
|
+
super({
|
|
708
|
+
...opts,
|
|
709
|
+
sdkName: (_a = opts.sdkName) != null ? _a : "opencode-plugin",
|
|
710
|
+
libraryName: (_b = opts.libraryName) != null ? _b : package_default.name,
|
|
711
|
+
libraryVersion: (_c = opts.libraryVersion) != null ? _c : package_default.version,
|
|
712
|
+
defaultEventName: (_d = opts.defaultEventName) != null ? _d : "opencode_session"
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
var TraceShipper2 = class extends TraceShipper {
|
|
717
|
+
constructor(opts) {
|
|
718
|
+
var _a, _b, _c;
|
|
719
|
+
super({
|
|
720
|
+
...opts,
|
|
721
|
+
sdkName: (_a = opts.sdkName) != null ? _a : "opencode-plugin",
|
|
722
|
+
serviceName: (_b = opts.serviceName) != null ? _b : "raindrop.opencode-plugin",
|
|
723
|
+
serviceVersion: (_c = opts.serviceVersion) != null ? _c : package_default.version
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
enqueue(span) {
|
|
727
|
+
var _a;
|
|
728
|
+
const attrs = (_a = span.attributes) != null ? _a : [];
|
|
729
|
+
attrs.unshift(
|
|
730
|
+
{ key: "span.id", value: { stringValue: span.spanId } },
|
|
731
|
+
...span.parentSpanId ? [{ key: "span.parent.id", value: { stringValue: span.parentSpanId } }] : []
|
|
732
|
+
);
|
|
733
|
+
span.attributes = attrs;
|
|
734
|
+
super.enqueue(span);
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
|
|
584
738
|
// src/tracing-linkage-helpers.ts
|
|
585
739
|
function createSessionParentMapHelpers({
|
|
586
740
|
sessions: sessions2,
|
|
@@ -1257,12 +1411,12 @@ async function plugin(input) {
|
|
|
1257
1411
|
if (config.debug) {
|
|
1258
1412
|
appLog("info", `Raindrop tracing enabled (endpoint: ${config.endpoint})`);
|
|
1259
1413
|
}
|
|
1260
|
-
const eventShipper = new
|
|
1414
|
+
const eventShipper = new EventShipper2({
|
|
1261
1415
|
writeKey: config.writeKey,
|
|
1262
1416
|
endpoint: config.endpoint,
|
|
1263
1417
|
debug: config.debug
|
|
1264
1418
|
});
|
|
1265
|
-
const traceShipper = new
|
|
1419
|
+
const traceShipper = new TraceShipper2({
|
|
1266
1420
|
writeKey: config.writeKey,
|
|
1267
1421
|
endpoint: config.endpoint,
|
|
1268
1422
|
debug: config.debug
|