@raindrop-ai/opencode-plugin 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.cjs +391 -172
  2. package/dist/index.js +391 -172
  3. package/package.json +8 -3
package/dist/index.js CHANGED
@@ -30,30 +30,34 @@ function loadConfig(projectDirectory) {
30
30
  writeKey: (_b = (_a = process.env["RAINDROP_WRITE_KEY"]) != null ? _a : merged.write_key) != null ? _b : "",
31
31
  endpoint: (_d = (_c = process.env["RAINDROP_API_URL"]) != null ? _c : merged.api_url) != null ? _d : "https://api.raindrop.ai/v1",
32
32
  eventName: (_e = merged.event_name) != null ? _e : "opencode_session",
33
- userId: merged.user_id,
34
33
  debug: process.env["RAINDROP_DEBUG"] === "true" ? true : (_f = merged.debug) != null ? _f : false
35
34
  };
36
35
  }
37
36
 
38
- // src/shipper.ts
39
- function getCryptoObj() {
40
- return globalThis.crypto;
37
+ // ../core/dist/chunk-FTBZHS25.js
38
+ function getCrypto() {
39
+ const c = globalThis.crypto;
40
+ return c;
41
41
  }
42
42
  function randomBytes(length) {
43
- const c = getCryptoObj();
43
+ const cryptoObj = getCrypto();
44
44
  const out = new Uint8Array(length);
45
- if (c == null ? void 0 : c.getRandomValues) {
46
- c.getRandomValues(out);
45
+ if (cryptoObj && typeof cryptoObj.getRandomValues === "function") {
46
+ cryptoObj.getRandomValues(out);
47
47
  return out;
48
48
  }
49
49
  for (let i = 0; i < out.length; i++) out[i] = Math.floor(Math.random() * 256);
50
50
  return out;
51
51
  }
52
52
  function base64Encode(bytes) {
53
- const buf = globalThis.Buffer;
54
- if (buf) return buf.from(bytes).toString("base64");
53
+ const maybeBuffer = globalThis.Buffer;
54
+ if (maybeBuffer) {
55
+ return maybeBuffer.from(bytes).toString("base64");
56
+ }
55
57
  let binary = "";
56
- for (let i2 = 0; i2 < bytes.length; i2++) binary += String.fromCharCode(bytes[i2]);
58
+ for (let i2 = 0; i2 < bytes.length; i2++) {
59
+ binary += String.fromCharCode(bytes[i2]);
60
+ }
57
61
  const btoaFn = globalThis.btoa;
58
62
  if (typeof btoaFn === "function") return btoaFn(binary);
59
63
  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
@@ -74,70 +78,9 @@ function base64Encode(bytes) {
74
78
  }
75
79
  return out;
76
80
  }
77
- var SERVICE_NAME = "raindrop.opencode-plugin";
78
- var PLUGIN_VERSION = "0.1.0";
79
- var SpanStatusCode = {
80
- ERROR: 2
81
- };
82
81
  function generateId() {
83
82
  return base64Encode(randomBytes(12)).replace(/[+/=]/g, "").slice(0, 16);
84
83
  }
85
- function createSpanIds(parent) {
86
- const traceId = parent ? parent.traceIdB64 : base64Encode(randomBytes(16));
87
- const spanId = base64Encode(randomBytes(8));
88
- return {
89
- traceIdB64: traceId,
90
- spanIdB64: spanId,
91
- parentSpanIdB64: parent ? parent.spanIdB64 : void 0
92
- };
93
- }
94
- function nowUnixNanoString() {
95
- return String(Date.now() * 1e6);
96
- }
97
- function attrString(key, value) {
98
- if (value === void 0) return void 0;
99
- return { key, value: { stringValue: value } };
100
- }
101
- function attrInt(key, value) {
102
- if (value === void 0) return void 0;
103
- if (!Number.isFinite(value)) return void 0;
104
- return { key, value: { intValue: String(Math.trunc(value)) } };
105
- }
106
- function buildOtlpSpan(args) {
107
- const attrs = [
108
- attrString("span.id", args.ids.spanIdB64),
109
- attrString("span.parent.id", args.ids.parentSpanIdB64),
110
- ...args.attributes
111
- ].filter((x) => x !== void 0);
112
- const span = {
113
- traceId: args.ids.traceIdB64,
114
- spanId: args.ids.spanIdB64,
115
- name: args.name,
116
- startTimeUnixNano: args.startTimeUnixNano,
117
- endTimeUnixNano: args.endTimeUnixNano
118
- };
119
- if (args.ids.parentSpanIdB64) span.parentSpanId = args.ids.parentSpanIdB64;
120
- if (attrs.length) span.attributes = attrs;
121
- if (args.status) span.status = args.status;
122
- return span;
123
- }
124
- function buildExportTraceServiceRequest(spans) {
125
- return {
126
- resourceSpans: [
127
- {
128
- resource: {
129
- attributes: [{ key: "service.name", value: { stringValue: SERVICE_NAME } }]
130
- },
131
- scopeSpans: [
132
- {
133
- scope: { name: SERVICE_NAME, version: PLUGIN_VERSION },
134
- spans
135
- }
136
- ]
137
- }
138
- ]
139
- };
140
- }
141
84
  function wait(ms) {
142
85
  return new Promise((resolve) => setTimeout(resolve, ms));
143
86
  }
@@ -150,7 +93,7 @@ function parseRetryAfter(headers) {
150
93
  const value = (_a = headers.get("Retry-After")) != null ? _a : headers.get("retry-after");
151
94
  if (!value) return void 0;
152
95
  const asNumber = Number(value);
153
- if (!Number.isNaN(asNumber)) return asNumber * 1e3;
96
+ if (value.trim() !== "" && !Number.isNaN(asNumber)) return asNumber * 1e3;
154
97
  const asDate = new Date(value).getTime();
155
98
  if (!Number.isNaN(asDate)) {
156
99
  const delta = asDate - Date.now();
@@ -159,26 +102,29 @@ function parseRetryAfter(headers) {
159
102
  return void 0;
160
103
  }
161
104
  function getRetryDelayMs(attemptNumber, previousError) {
162
- if (previousError !== null && typeof previousError === "object" && "retryAfterMs" in previousError) {
105
+ if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
163
106
  const v = previousError.retryAfterMs;
164
107
  if (typeof v === "number") return Math.max(0, v);
165
108
  }
166
109
  if (attemptNumber <= 1) return 0;
167
- return 500 * Math.pow(2, attemptNumber - 2);
110
+ const base = 500;
111
+ const factor = Math.pow(2, attemptNumber - 2);
112
+ return base * factor;
168
113
  }
169
114
  async function withRetry(operation, opName, opts) {
115
+ const prefix = opts.sdkName ? `[raindrop-ai/${opts.sdkName}]` : "[raindrop-ai/core]";
170
116
  let lastError = void 0;
171
- for (let attempt = 1; attempt <= opts.maxAttempts; attempt++) {
172
- if (attempt > 1) {
173
- const delay = getRetryDelayMs(attempt, lastError);
117
+ for (let attemptNumber = 1; attemptNumber <= opts.maxAttempts; attemptNumber++) {
118
+ if (attemptNumber > 1) {
119
+ const delay = getRetryDelayMs(attemptNumber, lastError);
174
120
  if (opts.debug) {
175
121
  console.warn(
176
- `[raindrop-ai/opencode-plugin] ${opName} retry ${attempt}/${opts.maxAttempts} in ${delay}ms`
122
+ `${prefix} ${opName} retry ${attemptNumber}/${opts.maxAttempts} in ${delay}ms`
177
123
  );
178
124
  }
179
125
  if (delay > 0) await wait(delay);
180
126
  } else if (opts.debug) {
181
- console.log(`[raindrop-ai/opencode-plugin] ${opName} attempt ${attempt}/${opts.maxAttempts}`);
127
+ console.log(`${prefix} ${opName} attempt ${attemptNumber}/${opts.maxAttempts}`);
182
128
  }
183
129
  try {
184
130
  return await operation();
@@ -187,20 +133,26 @@ async function withRetry(operation, opName, opts) {
187
133
  if (opts.debug) {
188
134
  const msg = err instanceof Error ? err.message : String(err);
189
135
  console.warn(
190
- `[raindrop-ai/opencode-plugin] ${opName} attempt ${attempt} failed: ${msg}${attempt === opts.maxAttempts ? " (no more retries)" : ""}`
136
+ `${prefix} ${opName} attempt ${attemptNumber} failed: ${msg}${attemptNumber === opts.maxAttempts ? " (no more retries)" : ""}`
191
137
  );
192
138
  }
193
- if (attempt === opts.maxAttempts) break;
139
+ if (lastError && typeof lastError === "object" && "retryable" in lastError && !lastError.retryable)
140
+ break;
141
+ if (attemptNumber === opts.maxAttempts) break;
194
142
  }
195
143
  }
196
144
  throw lastError instanceof Error ? lastError : new Error(String(lastError));
197
145
  }
198
146
  async function postJson(url, body, headers, opts) {
147
+ const opName = `POST ${url}`;
199
148
  await withRetry(
200
149
  async () => {
201
150
  const resp = await fetch(url, {
202
151
  method: "POST",
203
- headers: { "Content-Type": "application/json", ...headers },
152
+ headers: {
153
+ "Content-Type": "application/json",
154
+ ...headers
155
+ },
204
156
  body: JSON.stringify(body)
205
157
  });
206
158
  if (!resp.ok) {
@@ -210,13 +162,71 @@ async function postJson(url, body, headers, opts) {
210
162
  );
211
163
  const retryAfterMs = parseRetryAfter(resp.headers);
212
164
  if (typeof retryAfterMs === "number") err.retryAfterMs = retryAfterMs;
165
+ err.retryable = resp.status === 429 || resp.status >= 500;
213
166
  throw err;
214
167
  }
215
168
  },
216
- `POST ${url}`,
169
+ opName,
217
170
  opts
218
171
  );
219
172
  }
173
+ var SpanStatusCode = {
174
+ UNSET: 0,
175
+ OK: 1,
176
+ ERROR: 2
177
+ };
178
+ function createSpanIds(parent) {
179
+ const traceId = parent ? parent.traceIdB64 : base64Encode(randomBytes(16));
180
+ const spanId = base64Encode(randomBytes(8));
181
+ return {
182
+ traceIdB64: traceId,
183
+ spanIdB64: spanId,
184
+ parentSpanIdB64: parent ? parent.spanIdB64 : void 0
185
+ };
186
+ }
187
+ function nowUnixNanoString() {
188
+ return Date.now().toString() + "000000";
189
+ }
190
+ function attrString(key, value) {
191
+ if (value === void 0) return void 0;
192
+ return { key, value: { stringValue: value } };
193
+ }
194
+ function attrInt(key, value) {
195
+ if (value === void 0) return void 0;
196
+ if (!Number.isFinite(value)) return void 0;
197
+ return { key, value: { intValue: String(Math.trunc(value)) } };
198
+ }
199
+ function buildOtlpSpan(args) {
200
+ const attrs = args.attributes.filter((x) => x !== void 0);
201
+ const span = {
202
+ traceId: args.ids.traceIdB64,
203
+ spanId: args.ids.spanIdB64,
204
+ name: args.name,
205
+ startTimeUnixNano: args.startTimeUnixNano,
206
+ endTimeUnixNano: args.endTimeUnixNano
207
+ };
208
+ if (args.ids.parentSpanIdB64) span.parentSpanId = args.ids.parentSpanIdB64;
209
+ if (attrs.length) span.attributes = attrs;
210
+ if (args.status) span.status = args.status;
211
+ return span;
212
+ }
213
+ function buildExportTraceServiceRequest(spans, serviceName = "raindrop.core", serviceVersion = "0.0.0") {
214
+ return {
215
+ resourceSpans: [
216
+ {
217
+ resource: {
218
+ attributes: [{ key: "service.name", value: { stringValue: serviceName } }]
219
+ },
220
+ scopeSpans: [
221
+ {
222
+ scope: { name: serviceName, version: serviceVersion },
223
+ spans
224
+ }
225
+ ]
226
+ }
227
+ ]
228
+ };
229
+ }
220
230
  function mergePatches(target, source) {
221
231
  var _a, _b, _c, _d;
222
232
  const out = { ...target, ...source };
@@ -234,34 +244,58 @@ var EventShipper = class {
234
244
  this.sticky = /* @__PURE__ */ new Map();
235
245
  this.timers = /* @__PURE__ */ new Map();
236
246
  this.inFlight = /* @__PURE__ */ new Set();
237
- var _a, _b;
238
- this.writeKey = opts.writeKey;
239
- this.baseUrl = (_a = formatEndpoint(opts.endpoint)) != null ? _a : "https://api.raindrop.ai/v1/";
247
+ var _a, _b, _c, _d, _e, _f, _g;
248
+ this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
249
+ this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
250
+ this.enabled = opts.enabled !== false;
240
251
  this.debug = opts.debug;
241
- this.partialFlushMs = (_b = opts.partialFlushMs) != null ? _b : 1e3;
252
+ this.partialFlushMs = (_c = opts.partialFlushMs) != null ? _c : 1e3;
253
+ this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
254
+ this.prefix = `[raindrop-ai/${this.sdkName}]`;
255
+ this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
256
+ const isNode = typeof process !== "undefined" && typeof process.version === "string";
257
+ this.context = {
258
+ library: {
259
+ name: (_f = opts.libraryName) != null ? _f : "@raindrop-ai/core",
260
+ version: (_g = opts.libraryVersion) != null ? _g : "0.0.0"
261
+ },
262
+ metadata: {
263
+ jsRuntime: isNode ? "node" : "web",
264
+ ...isNode ? { nodeVersion: process.version } : {}
265
+ }
266
+ };
267
+ }
268
+ isDebugEnabled() {
269
+ return this.debug;
270
+ }
271
+ authHeaders() {
272
+ return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
242
273
  }
243
274
  async patch(eventId, patch) {
244
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
245
- if (!eventId) return;
275
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
276
+ if (!this.enabled) return;
277
+ if (!eventId || !eventId.trim()) return;
246
278
  if (this.debug) {
247
- console.log("[raindrop-ai/opencode-plugin] queue patch", {
279
+ console.log(`${this.prefix} queue patch`, {
248
280
  eventId,
249
281
  userId: patch.userId,
282
+ eventName: patch.eventName,
250
283
  hasInput: typeof patch.input === "string" && patch.input.length > 0,
251
284
  hasOutput: typeof patch.output === "string" && patch.output.length > 0,
285
+ attachments: (_b = (_a = patch.attachments) == null ? void 0 : _a.length) != null ? _b : 0,
252
286
  isPending: patch.isPending
253
287
  });
254
288
  }
255
- const sticky = (_a = this.sticky.get(eventId)) != null ? _a : {};
256
- const existing = (_b = this.buffers.get(eventId)) != null ? _b : {};
289
+ const sticky = (_c = this.sticky.get(eventId)) != null ? _c : {};
290
+ const existing = (_d = this.buffers.get(eventId)) != null ? _d : {};
257
291
  const merged = mergePatches(existing, patch);
258
- merged.isPending = (_e = (_d = (_c = patch.isPending) != null ? _c : existing.isPending) != null ? _d : sticky.isPending) != null ? _e : true;
292
+ merged.isPending = (_g = (_f = (_e = patch.isPending) != null ? _e : existing.isPending) != null ? _f : sticky.isPending) != null ? _g : true;
259
293
  this.buffers.set(eventId, merged);
260
294
  this.sticky.set(eventId, {
261
- userId: (_f = merged.userId) != null ? _f : sticky.userId,
262
- convoId: (_g = merged.convoId) != null ? _g : sticky.convoId,
263
- eventName: (_h = merged.eventName) != null ? _h : sticky.eventName,
264
- isPending: (_i = merged.isPending) != null ? _i : sticky.isPending
295
+ userId: (_h = merged.userId) != null ? _h : sticky.userId,
296
+ convoId: (_i = merged.convoId) != null ? _i : sticky.convoId,
297
+ eventName: (_j = merged.eventName) != null ? _j : sticky.eventName,
298
+ isPending: (_k = merged.isPending) != null ? _k : sticky.isPending
265
299
  });
266
300
  const t = this.timers.get(eventId);
267
301
  if (t) clearTimeout(t);
@@ -279,6 +313,7 @@ var EventShipper = class {
279
313
  await this.patch(eventId, { ...patch, isPending: false });
280
314
  }
281
315
  async flush() {
316
+ if (!this.enabled) return;
282
317
  const ids = [...this.buffers.keys()];
283
318
  await Promise.all(ids.map((id) => this.flushOne(id)));
284
319
  await Promise.all([...this.inFlight].map((p) => p.catch(() => {
@@ -289,8 +324,70 @@ var EventShipper = class {
289
324
  this.timers.clear();
290
325
  await this.flush();
291
326
  }
327
+ async trackSignal(signal) {
328
+ var _a, _b;
329
+ if (!this.enabled) return;
330
+ const body = [
331
+ {
332
+ event_id: signal.eventId,
333
+ signal_name: signal.name,
334
+ signal_type: (_a = signal.type) != null ? _a : "default",
335
+ timestamp: signal.timestamp,
336
+ sentiment: signal.sentiment,
337
+ attachment_id: signal.attachmentId,
338
+ properties: {
339
+ ...(_b = signal.properties) != null ? _b : {},
340
+ ...signal.comment ? { comment: signal.comment } : {},
341
+ ...signal.after ? { after: signal.after } : {}
342
+ }
343
+ }
344
+ ];
345
+ const url = `${this.baseUrl}signals/track`;
346
+ try {
347
+ await postJson(url, body, this.authHeaders(), {
348
+ maxAttempts: 3,
349
+ debug: this.debug,
350
+ sdkName: this.sdkName
351
+ });
352
+ } catch (err) {
353
+ const msg = err instanceof Error ? err.message : String(err);
354
+ console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`);
355
+ }
356
+ }
357
+ async identify(users) {
358
+ if (!this.enabled) return;
359
+ const list = Array.isArray(users) ? users : [users];
360
+ const body = list.filter((user) => {
361
+ if (!(user == null ? void 0 : user.userId) || !user.userId.trim()) {
362
+ if (this.debug) {
363
+ console.warn(`${this.prefix} skipping identify: missing userId`);
364
+ }
365
+ return false;
366
+ }
367
+ return true;
368
+ }).map((user) => {
369
+ var _a;
370
+ return {
371
+ user_id: user.userId,
372
+ traits: (_a = user.traits) != null ? _a : {}
373
+ };
374
+ });
375
+ if (body.length === 0) return;
376
+ const url = `${this.baseUrl}users/identify`;
377
+ try {
378
+ await postJson(url, body, this.authHeaders(), {
379
+ maxAttempts: 3,
380
+ debug: this.debug,
381
+ sdkName: this.sdkName
382
+ });
383
+ } catch (err) {
384
+ const msg = err instanceof Error ? err.message : String(err);
385
+ console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`);
386
+ }
387
+ }
292
388
  async flushOne(eventId) {
293
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
389
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
390
+ if (!this.enabled) return;
294
391
  const timer = this.timers.get(eventId);
295
392
  if (timer) {
296
393
  clearTimeout(timer);
@@ -300,105 +397,123 @@ var EventShipper = class {
300
397
  this.buffers.delete(eventId);
301
398
  if (!accumulated) return;
302
399
  const sticky = (_a = this.sticky.get(eventId)) != null ? _a : {};
303
- const eventName = (_c = (_b = accumulated.eventName) != null ? _b : sticky.eventName) != null ? _c : "opencode_session";
400
+ const eventName = (_c = (_b = accumulated.eventName) != null ? _b : sticky.eventName) != null ? _c : this.defaultEventName;
304
401
  const userId = (_d = accumulated.userId) != null ? _d : sticky.userId;
305
402
  if (!userId) {
306
403
  if (this.debug) {
307
- console.warn(
308
- `[raindrop-ai/opencode-plugin] skipping track_partial for ${eventId}: missing userId`
309
- );
404
+ console.warn(`${this.prefix} skipping track_partial for ${eventId}: missing userId`);
310
405
  }
406
+ this.sticky.delete(eventId);
311
407
  return;
312
408
  }
409
+ const { wizardSession, ...restProperties } = (_e = accumulated.properties) != null ? _e : {};
313
410
  const payload = {
314
411
  event_id: eventId,
315
412
  user_id: userId,
316
413
  event: eventName,
317
- timestamp: (_e = accumulated.timestamp) != null ? _e : (/* @__PURE__ */ new Date()).toISOString(),
414
+ timestamp: (_f = accumulated.timestamp) != null ? _f : (/* @__PURE__ */ new Date()).toISOString(),
318
415
  ai_data: {
319
416
  input: accumulated.input,
320
417
  output: accumulated.output,
321
418
  model: accumulated.model,
322
- convo_id: (_f = accumulated.convoId) != null ? _f : sticky.convoId
419
+ convo_id: (_g = accumulated.convoId) != null ? _g : sticky.convoId
323
420
  },
324
421
  properties: {
325
- ...(_g = accumulated.properties) != null ? _g : {},
326
- $context: {
327
- library: { name: "@raindrop-ai/opencode-plugin", version: PLUGIN_VERSION },
328
- metadata: { jsRuntime: "node" }
329
- }
422
+ ...restProperties,
423
+ ...wizardSession ? { "raindrop.wizardSession": wizardSession } : {},
424
+ $context: this.context
330
425
  },
331
426
  attachments: accumulated.attachments,
332
427
  is_pending: ((_i = (_h = accumulated.isPending) != null ? _h : sticky.isPending) != null ? _i : true) !== false
333
428
  };
334
429
  const url = `${this.baseUrl}events/track_partial`;
335
430
  if (this.debug) {
336
- console.log("[raindrop-ai/opencode-plugin] sending track_partial", {
431
+ console.log(`${this.prefix} sending track_partial`, {
337
432
  eventId,
338
433
  eventName,
339
434
  userId,
340
- isPending: payload["is_pending"],
435
+ isPending: payload.is_pending,
341
436
  inputPreview: typeof accumulated.input === "string" ? accumulated.input.slice(0, 120) : void 0,
342
437
  outputPreview: typeof accumulated.output === "string" ? accumulated.output.slice(0, 120) : void 0,
438
+ attachments: (_k = (_j = accumulated.attachments) == null ? void 0 : _j.length) != null ? _k : 0,
439
+ attachmentKinds: (_m = (_l = accumulated.attachments) == null ? void 0 : _l.map((a) => ({
440
+ type: a.type,
441
+ role: a.role,
442
+ name: a.name,
443
+ valuePreview: a.value.slice(0, 60)
444
+ }))) != null ? _m : [],
343
445
  endpoint: url
344
446
  });
345
447
  }
346
- const p = postJson(
347
- url,
348
- payload,
349
- { Authorization: `Bearer ${this.writeKey}` },
350
- { maxAttempts: 3, debug: this.debug }
351
- );
448
+ const p = postJson(url, payload, this.authHeaders(), {
449
+ maxAttempts: 3,
450
+ debug: this.debug,
451
+ sdkName: this.sdkName
452
+ });
352
453
  this.inFlight.add(p);
353
454
  try {
354
455
  try {
355
456
  await p;
356
457
  if (this.debug) {
357
- console.log(`[raindrop-ai/opencode-plugin] sent track_partial ${eventId} (${eventName})`);
458
+ console.log(`${this.prefix} sent track_partial ${eventId} (${eventName})`);
358
459
  }
359
460
  } catch (err) {
360
- if (this.debug) {
361
- const msg = err instanceof Error ? err.message : String(err);
362
- console.warn(
363
- `[raindrop-ai/opencode-plugin] failed to send track_partial (dropping): ${msg}`
364
- );
365
- }
461
+ const msg = err instanceof Error ? err.message : String(err);
462
+ console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`);
366
463
  }
367
464
  } finally {
368
465
  this.inFlight.delete(p);
369
466
  }
370
- if (!payload["is_pending"]) this.sticky.delete(eventId);
467
+ const isPending = payload.is_pending;
468
+ if (!isPending) {
469
+ this.sticky.delete(eventId);
470
+ }
371
471
  }
372
472
  };
373
473
  var TraceShipper = class {
374
474
  constructor(opts) {
375
475
  this.queue = [];
376
476
  this.inFlight = /* @__PURE__ */ new Set();
377
- var _a, _b;
378
- this.writeKey = opts.writeKey;
379
- this.baseUrl = (_a = formatEndpoint(opts.endpoint)) != null ? _a : "https://api.raindrop.ai/v1/";
477
+ var _a, _b, _c, _d, _e, _f, _g, _h;
478
+ this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
479
+ this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
480
+ this.enabled = opts.enabled !== false;
380
481
  this.debug = opts.debug;
381
- this.flushIntervalMs = (_b = opts.flushIntervalMs) != null ? _b : 1e3;
382
- this.maxBatchSize = 50;
383
- this.maxQueueSize = 5e3;
482
+ this.debugSpans = opts.debugSpans === true;
483
+ this.flushIntervalMs = (_c = opts.flushIntervalMs) != null ? _c : 1e3;
484
+ this.maxBatchSize = (_d = opts.maxBatchSize) != null ? _d : 50;
485
+ this.maxQueueSize = (_e = opts.maxQueueSize) != null ? _e : 5e3;
486
+ this.sdkName = (_f = opts.sdkName) != null ? _f : "core";
487
+ this.prefix = `[raindrop-ai/${this.sdkName}]`;
488
+ this.serviceName = (_g = opts.serviceName) != null ? _g : "raindrop.core";
489
+ this.serviceVersion = (_h = opts.serviceVersion) != null ? _h : "0.0.0";
490
+ }
491
+ isDebugEnabled() {
492
+ return this.debug;
493
+ }
494
+ authHeaders() {
495
+ return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
384
496
  }
385
497
  startSpan(args) {
386
- var _a;
498
+ var _a, _b;
387
499
  const ids = createSpanIds(args.parent);
388
- const started = nowUnixNanoString();
500
+ const started = (_a = args.startTimeUnixNano) != null ? _a : nowUnixNanoString();
389
501
  const attrs = [
390
- attrString("ai.telemetry.metadata.raindrop.eventId", args.eventId)
502
+ attrString("ai.telemetry.metadata.raindrop.eventId", args.eventId),
503
+ attrString("ai.operationId", args.operationId)
391
504
  ];
392
- if ((_a = args.attributes) == null ? void 0 : _a.length) attrs.push(...args.attributes);
505
+ if ((_b = args.attributes) == null ? void 0 : _b.length) attrs.push(...args.attributes);
393
506
  return { ids, name: args.name, startTimeUnixNano: started, attributes: attrs };
394
507
  }
395
508
  endSpan(span, extra) {
396
- var _a;
509
+ var _a, _b;
397
510
  if (span.endTimeUnixNano) return;
398
- span.endTimeUnixNano = nowUnixNanoString();
399
- if ((_a = extra == null ? void 0 : extra.attributes) == null ? void 0 : _a.length) span.attributes.push(...extra.attributes);
400
- let status;
401
- if ((extra == null ? void 0 : extra.error) !== void 0) {
511
+ span.endTimeUnixNano = (_a = extra == null ? void 0 : extra.endTimeUnixNano) != null ? _a : nowUnixNanoString();
512
+ if ((_b = extra == null ? void 0 : extra.attributes) == null ? void 0 : _b.length) {
513
+ span.attributes.push(...extra.attributes);
514
+ }
515
+ let status = extra == null ? void 0 : extra.status;
516
+ if (!status && (extra == null ? void 0 : extra.error) !== void 0) {
402
517
  const message = extra.error instanceof Error ? extra.error.message : String(extra.error);
403
518
  status = { code: SpanStatusCode.ERROR, message };
404
519
  }
@@ -412,10 +527,6 @@ var TraceShipper = class {
412
527
  });
413
528
  this.enqueue(otlp);
414
529
  }
415
- /**
416
- * Create and enqueue a span with both start and end times (atomic insert).
417
- * Used for tool spans and any span whose full duration is known at write time.
418
- */
419
530
  createSpan(args) {
420
531
  var _a;
421
532
  const ids = createSpanIds(args.parent);
@@ -434,7 +545,18 @@ var TraceShipper = class {
434
545
  this.enqueue(otlp);
435
546
  }
436
547
  enqueue(span) {
437
- if (this.queue.length >= this.maxQueueSize) this.queue.shift();
548
+ if (!this.enabled) return;
549
+ if (this.debugSpans) {
550
+ const short = (s) => s ? s.slice(-8) : "none";
551
+ console.log(
552
+ `${this.prefix}[span] name=${span.name} trace=${short(span.traceId)} span=${short(span.spanId)} parent=${short(
553
+ span.parentSpanId
554
+ )}`
555
+ );
556
+ }
557
+ if (this.queue.length >= this.maxQueueSize) {
558
+ this.queue.shift();
559
+ }
438
560
  this.queue.push(span);
439
561
  if (this.queue.length >= this.maxBatchSize) {
440
562
  void this.flush().catch(() => {
@@ -450,42 +572,34 @@ var TraceShipper = class {
450
572
  }
451
573
  }
452
574
  async flush() {
575
+ if (!this.enabled) return;
453
576
  if (this.timer) {
454
577
  clearTimeout(this.timer);
455
578
  this.timer = void 0;
456
579
  }
457
580
  while (this.queue.length > 0) {
458
581
  const batch = this.queue.splice(0, this.maxBatchSize);
459
- const body = buildExportTraceServiceRequest(batch);
582
+ const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
460
583
  const url = `${this.baseUrl}traces`;
461
- const spanNames = batch.map((span) => span.name);
462
584
  if (this.debug) {
463
- console.log("[raindrop-ai/opencode-plugin] sending traces batch", {
585
+ console.log(`${this.prefix} sending traces batch`, {
464
586
  spans: batch.length,
465
- endpoint: url,
466
- spanNames
587
+ endpoint: url
467
588
  });
468
589
  }
469
- const p = postJson(
470
- url,
471
- body,
472
- { Authorization: `Bearer ${this.writeKey}` },
473
- { maxAttempts: 3, debug: this.debug }
474
- );
590
+ const p = postJson(url, body, this.authHeaders(), {
591
+ maxAttempts: 3,
592
+ debug: this.debug,
593
+ sdkName: this.sdkName
594
+ });
475
595
  this.inFlight.add(p);
476
596
  try {
477
597
  try {
478
598
  await p;
479
- if (this.debug) {
480
- console.log(`[raindrop-ai/opencode-plugin] sent ${batch.length} spans`, {
481
- spanNames
482
- });
483
- }
599
+ if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
484
600
  } catch (err) {
485
- if (this.debug) {
486
- const msg = err instanceof Error ? err.message : String(err);
487
- console.warn(`[raindrop-ai/opencode-plugin] failed to send spans (dropping): ${msg}`);
488
- }
601
+ const msg = err instanceof Error ? err.message : String(err);
602
+ console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`);
489
603
  }
490
604
  } finally {
491
605
  this.inFlight.delete(p);
@@ -503,6 +617,105 @@ var TraceShipper = class {
503
617
  }
504
618
  };
505
619
 
620
+ // package.json
621
+ var package_default = {
622
+ name: "@raindrop-ai/opencode-plugin",
623
+ version: "0.0.5",
624
+ description: "Raindrop observability plugin for OpenCode \u2014 automatic session/event/span tracing",
625
+ type: "module",
626
+ main: "dist/index.cjs",
627
+ module: "dist/index.js",
628
+ types: "dist/index.d.ts",
629
+ exports: {
630
+ ".": {
631
+ types: "./dist/index.d.ts",
632
+ import: "./dist/index.js",
633
+ require: "./dist/index.cjs"
634
+ }
635
+ },
636
+ sideEffects: false,
637
+ files: [
638
+ "dist/**"
639
+ ],
640
+ scripts: {
641
+ build: "tsup",
642
+ dev: "tsup --watch",
643
+ clean: "rm -rf dist",
644
+ test: "cd tests && yarn test"
645
+ },
646
+ peerDependencies: {
647
+ "@opencode-ai/plugin": ">=1.2.0",
648
+ "@opencode-ai/sdk": ">=1.2.0"
649
+ },
650
+ peerDependenciesMeta: {
651
+ "@opencode-ai/sdk": {
652
+ optional: true
653
+ }
654
+ },
655
+ devDependencies: {
656
+ "@raindrop-ai/core": "*",
657
+ "@opencode-ai/plugin": "^1.2.0",
658
+ "@opencode-ai/sdk": "^1.2.0",
659
+ "@types/node": "^20.11.17",
660
+ tsup: "^8.4.0",
661
+ typescript: "^5.3.3"
662
+ },
663
+ tsup: {
664
+ entry: [
665
+ "src/index.ts"
666
+ ],
667
+ format: [
668
+ "cjs",
669
+ "esm"
670
+ ],
671
+ dts: {
672
+ resolve: true
673
+ },
674
+ clean: true,
675
+ noExternal: [
676
+ "@raindrop-ai/core"
677
+ ]
678
+ },
679
+ publishConfig: {
680
+ access: "public"
681
+ }
682
+ };
683
+
684
+ // src/shipper.ts
685
+ var EventShipper2 = class extends EventShipper {
686
+ constructor(opts) {
687
+ var _a, _b, _c, _d;
688
+ super({
689
+ ...opts,
690
+ sdkName: (_a = opts.sdkName) != null ? _a : "opencode-plugin",
691
+ libraryName: (_b = opts.libraryName) != null ? _b : package_default.name,
692
+ libraryVersion: (_c = opts.libraryVersion) != null ? _c : package_default.version,
693
+ defaultEventName: (_d = opts.defaultEventName) != null ? _d : "opencode_session"
694
+ });
695
+ }
696
+ };
697
+ var TraceShipper2 = class extends TraceShipper {
698
+ constructor(opts) {
699
+ var _a, _b, _c;
700
+ super({
701
+ ...opts,
702
+ sdkName: (_a = opts.sdkName) != null ? _a : "opencode-plugin",
703
+ serviceName: (_b = opts.serviceName) != null ? _b : "raindrop.opencode-plugin",
704
+ serviceVersion: (_c = opts.serviceVersion) != null ? _c : package_default.version
705
+ });
706
+ }
707
+ enqueue(span) {
708
+ var _a;
709
+ const attrs = (_a = span.attributes) != null ? _a : [];
710
+ attrs.unshift(
711
+ { key: "span.id", value: { stringValue: span.spanId } },
712
+ ...span.parentSpanId ? [{ key: "span.parent.id", value: { stringValue: span.parentSpanId } }] : []
713
+ );
714
+ span.attributes = attrs;
715
+ super.enqueue(span);
716
+ }
717
+ };
718
+
506
719
  // src/tracing-linkage-helpers.ts
507
720
  function createSessionParentMapHelpers({
508
721
  sessions: sessions2,
@@ -614,8 +827,8 @@ function getHostname() {
614
827
  }
615
828
  function createHooks(config, worktree, directory, eventShipper, traceShipper) {
616
829
  function log(msg, data) {
617
- const level = config.debug ? "info" : "debug";
618
- const prefix = `[raindrop-ai/opencode-plugin] [${level}] ${msg}`;
830
+ if (!config.debug) return;
831
+ const prefix = `[raindrop-ai/opencode-plugin] [info] ${msg}`;
619
832
  if (data !== void 0) {
620
833
  console.log(prefix, data);
621
834
  return;
@@ -786,7 +999,7 @@ type: ${name != null ? name : "UnknownError"}`;
786
999
  attributes: llmAttrs
787
1000
  });
788
1001
  if (typeof timeObj["created"] === "number") {
789
- llmSpan.startTimeUnixNano = String(timeObj["created"] * 1e6);
1002
+ llmSpan.startTimeUnixNano = String(Math.floor(timeObj["created"])) + "000000";
790
1003
  }
791
1004
  traceShipper.endSpan(llmSpan, { error: errorForSpan });
792
1005
  await traceShipper.flush();
@@ -823,7 +1036,7 @@ type: ${name != null ? name : "UnknownError"}`;
823
1036
  attributes: llmAttrs
824
1037
  });
825
1038
  if (typeof timeObj["created"] === "number") {
826
- finalLlmSpan.startTimeUnixNano = String(timeObj["created"] * 1e6);
1039
+ finalLlmSpan.startTimeUnixNano = String(Math.floor(timeObj["created"])) + "000000";
827
1040
  }
828
1041
  traceShipper.endSpan(finalLlmSpan, { error: errorForSpan });
829
1042
  traceShipper.endSpan(rootSpan, {
@@ -883,7 +1096,7 @@ type: ${name != null ? name : "UnknownError"}`;
883
1096
  if (state.currentEventId) {
884
1097
  state.currentEventId = void 0;
885
1098
  }
886
- await traceShipper.flush();
1099
+ await Promise.all([eventShipper.flush(), traceShipper.flush()]);
887
1100
  if (state.parentId) {
888
1101
  cleanupSessionParentMap(String(sessionID));
889
1102
  sessions.delete(String(sessionID));
@@ -1080,6 +1293,9 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
1080
1293
  });
1081
1294
  }
1082
1295
  } catch (err) {
1296
+ console.log(
1297
+ `[raindrop-ai/opencode-plugin] [error] Error in tool.execute.before hook: ${err instanceof Error ? err.message : String(err)}`
1298
+ );
1083
1299
  }
1084
1300
  },
1085
1301
  // ------------------------------------------------------------------
@@ -1151,6 +1367,9 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
1151
1367
  });
1152
1368
  }
1153
1369
  } catch (err) {
1370
+ console.log(
1371
+ `[raindrop-ai/opencode-plugin] [error] Error in tool.execute.after hook: ${err instanceof Error ? err.message : String(err)}`
1372
+ );
1154
1373
  }
1155
1374
  }
1156
1375
  };
@@ -1173,12 +1392,12 @@ async function plugin(input) {
1173
1392
  if (config.debug) {
1174
1393
  appLog("info", `Raindrop tracing enabled (endpoint: ${config.endpoint})`);
1175
1394
  }
1176
- const eventShipper = new EventShipper({
1395
+ const eventShipper = new EventShipper2({
1177
1396
  writeKey: config.writeKey,
1178
1397
  endpoint: config.endpoint,
1179
1398
  debug: config.debug
1180
1399
  });
1181
- const traceShipper = new TraceShipper({
1400
+ const traceShipper = new TraceShipper2({
1182
1401
  writeKey: config.writeKey,
1183
1402
  endpoint: config.endpoint,
1184
1403
  debug: config.debug