@raindrop-ai/opencode-plugin 0.0.4 → 0.0.6

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 +364 -191
  2. package/dist/index.js +364 -191
  3. package/package.json +7 -3
package/dist/index.js CHANGED
@@ -26,18 +26,27 @@ function loadConfig(projectDirectory) {
26
26
  } catch (e) {
27
27
  }
28
28
  }
29
+ let eventMetadata;
30
+ const envMeta = process.env["RAINDROP_EVENT_METADATA"];
31
+ if (envMeta) {
32
+ try {
33
+ eventMetadata = JSON.parse(envMeta);
34
+ } catch (e) {
35
+ }
36
+ }
29
37
  return {
30
38
  writeKey: (_b = (_a = process.env["RAINDROP_WRITE_KEY"]) != null ? _a : merged.write_key) != null ? _b : "",
31
39
  endpoint: (_d = (_c = process.env["RAINDROP_API_URL"]) != null ? _c : merged.api_url) != null ? _d : "https://api.raindrop.ai/v1",
32
40
  eventName: (_e = merged.event_name) != null ? _e : "opencode_session",
33
- debug: process.env["RAINDROP_DEBUG"] === "true" ? true : (_f = merged.debug) != null ? _f : false
41
+ debug: process.env["RAINDROP_DEBUG"] === "true" ? true : (_f = merged.debug) != null ? _f : false,
42
+ eventMetadata
34
43
  };
35
44
  }
36
45
 
37
46
  // package.json
38
47
  var package_default = {
39
48
  name: "@raindrop-ai/opencode-plugin",
40
- version: "0.0.4",
49
+ version: "0.0.6",
41
50
  description: "Raindrop observability plugin for OpenCode \u2014 automatic session/event/span tracing",
42
51
  type: "module",
43
52
  main: "dist/index.cjs",
@@ -58,7 +67,7 @@ var package_default = {
58
67
  build: "tsup",
59
68
  dev: "tsup --watch",
60
69
  clean: "rm -rf dist",
61
- test: "cd tests && yarn test"
70
+ test: "cd tests && pnpm test"
62
71
  },
63
72
  peerDependencies: {
64
73
  "@opencode-ai/plugin": ">=1.2.0",
@@ -70,6 +79,7 @@ var package_default = {
70
79
  }
71
80
  },
72
81
  devDependencies: {
82
+ "@raindrop-ai/core": "workspace:*",
73
83
  "@opencode-ai/plugin": "^1.2.0",
74
84
  "@opencode-ai/sdk": "^1.2.0",
75
85
  "@types/node": "^20.11.17",
@@ -87,33 +97,44 @@ var package_default = {
87
97
  dts: {
88
98
  resolve: true
89
99
  },
90
- clean: true
100
+ clean: true,
101
+ noExternal: [
102
+ "@raindrop-ai/core"
103
+ ]
91
104
  },
92
105
  publishConfig: {
93
106
  access: "public"
94
107
  }
95
108
  };
96
109
 
97
- // src/shipper.ts
110
+ // src/package-info.ts
111
+ var PLUGIN_NAME = package_default.name;
98
112
  var PLUGIN_VERSION = package_default.version;
99
- function getCryptoObj() {
100
- return globalThis.crypto;
113
+
114
+ // ../core/dist/chunk-FTBZHS25.js
115
+ function getCrypto() {
116
+ const c = globalThis.crypto;
117
+ return c;
101
118
  }
102
119
  function randomBytes(length) {
103
- const c = getCryptoObj();
120
+ const cryptoObj = getCrypto();
104
121
  const out = new Uint8Array(length);
105
- if (c == null ? void 0 : c.getRandomValues) {
106
- c.getRandomValues(out);
122
+ if (cryptoObj && typeof cryptoObj.getRandomValues === "function") {
123
+ cryptoObj.getRandomValues(out);
107
124
  return out;
108
125
  }
109
126
  for (let i = 0; i < out.length; i++) out[i] = Math.floor(Math.random() * 256);
110
127
  return out;
111
128
  }
112
129
  function base64Encode(bytes) {
113
- const buf = globalThis.Buffer;
114
- if (buf) return buf.from(bytes).toString("base64");
130
+ const maybeBuffer = globalThis.Buffer;
131
+ if (maybeBuffer) {
132
+ return maybeBuffer.from(bytes).toString("base64");
133
+ }
115
134
  let binary = "";
116
- for (let i2 = 0; i2 < bytes.length; i2++) binary += String.fromCharCode(bytes[i2]);
135
+ for (let i2 = 0; i2 < bytes.length; i2++) {
136
+ binary += String.fromCharCode(bytes[i2]);
137
+ }
117
138
  const btoaFn = globalThis.btoa;
118
139
  if (typeof btoaFn === "function") return btoaFn(binary);
119
140
  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
@@ -134,69 +155,9 @@ function base64Encode(bytes) {
134
155
  }
135
156
  return out;
136
157
  }
137
- var SERVICE_NAME = "raindrop.opencode-plugin";
138
- var SpanStatusCode = {
139
- ERROR: 2
140
- };
141
158
  function generateId() {
142
159
  return base64Encode(randomBytes(12)).replace(/[+/=]/g, "").slice(0, 16);
143
160
  }
144
- function createSpanIds(parent) {
145
- const traceId = parent ? parent.traceIdB64 : base64Encode(randomBytes(16));
146
- const spanId = base64Encode(randomBytes(8));
147
- return {
148
- traceIdB64: traceId,
149
- spanIdB64: spanId,
150
- parentSpanIdB64: parent ? parent.spanIdB64 : void 0
151
- };
152
- }
153
- function nowUnixNanoString() {
154
- return Date.now().toString() + "000000";
155
- }
156
- function attrString(key, value) {
157
- if (value === void 0) return void 0;
158
- return { key, value: { stringValue: value } };
159
- }
160
- function attrInt(key, value) {
161
- if (value === void 0) return void 0;
162
- if (!Number.isFinite(value)) return void 0;
163
- return { key, value: { intValue: String(Math.trunc(value)) } };
164
- }
165
- function buildOtlpSpan(args) {
166
- const attrs = [
167
- attrString("span.id", args.ids.spanIdB64),
168
- attrString("span.parent.id", args.ids.parentSpanIdB64),
169
- ...args.attributes
170
- ].filter((x) => x !== void 0);
171
- const span = {
172
- traceId: args.ids.traceIdB64,
173
- spanId: args.ids.spanIdB64,
174
- name: args.name,
175
- startTimeUnixNano: args.startTimeUnixNano,
176
- endTimeUnixNano: args.endTimeUnixNano
177
- };
178
- if (args.ids.parentSpanIdB64) span.parentSpanId = args.ids.parentSpanIdB64;
179
- if (attrs.length) span.attributes = attrs;
180
- if (args.status) span.status = args.status;
181
- return span;
182
- }
183
- function buildExportTraceServiceRequest(spans) {
184
- return {
185
- resourceSpans: [
186
- {
187
- resource: {
188
- attributes: [{ key: "service.name", value: { stringValue: SERVICE_NAME } }]
189
- },
190
- scopeSpans: [
191
- {
192
- scope: { name: SERVICE_NAME, version: PLUGIN_VERSION },
193
- spans
194
- }
195
- ]
196
- }
197
- ]
198
- };
199
- }
200
161
  function wait(ms) {
201
162
  return new Promise((resolve) => setTimeout(resolve, ms));
202
163
  }
@@ -209,7 +170,7 @@ function parseRetryAfter(headers) {
209
170
  const value = (_a = headers.get("Retry-After")) != null ? _a : headers.get("retry-after");
210
171
  if (!value) return void 0;
211
172
  const asNumber = Number(value);
212
- if (!Number.isNaN(asNumber)) return asNumber * 1e3;
173
+ if (value.trim() !== "" && !Number.isNaN(asNumber)) return asNumber * 1e3;
213
174
  const asDate = new Date(value).getTime();
214
175
  if (!Number.isNaN(asDate)) {
215
176
  const delta = asDate - Date.now();
@@ -218,26 +179,29 @@ function parseRetryAfter(headers) {
218
179
  return void 0;
219
180
  }
220
181
  function getRetryDelayMs(attemptNumber, previousError) {
221
- if (previousError !== null && typeof previousError === "object" && "retryAfterMs" in previousError) {
182
+ if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
222
183
  const v = previousError.retryAfterMs;
223
184
  if (typeof v === "number") return Math.max(0, v);
224
185
  }
225
186
  if (attemptNumber <= 1) return 0;
226
- return 500 * Math.pow(2, attemptNumber - 2);
187
+ const base = 500;
188
+ const factor = Math.pow(2, attemptNumber - 2);
189
+ return base * factor;
227
190
  }
228
191
  async function withRetry(operation, opName, opts) {
192
+ const prefix = opts.sdkName ? `[raindrop-ai/${opts.sdkName}]` : "[raindrop-ai/core]";
229
193
  let lastError = void 0;
230
- for (let attempt = 1; attempt <= opts.maxAttempts; attempt++) {
231
- if (attempt > 1) {
232
- const delay = getRetryDelayMs(attempt, lastError);
194
+ for (let attemptNumber = 1; attemptNumber <= opts.maxAttempts; attemptNumber++) {
195
+ if (attemptNumber > 1) {
196
+ const delay = getRetryDelayMs(attemptNumber, lastError);
233
197
  if (opts.debug) {
234
198
  console.warn(
235
- `[raindrop-ai/opencode-plugin] ${opName} retry ${attempt}/${opts.maxAttempts} in ${delay}ms`
199
+ `${prefix} ${opName} retry ${attemptNumber}/${opts.maxAttempts} in ${delay}ms`
236
200
  );
237
201
  }
238
202
  if (delay > 0) await wait(delay);
239
203
  } else if (opts.debug) {
240
- console.log(`[raindrop-ai/opencode-plugin] ${opName} attempt ${attempt}/${opts.maxAttempts}`);
204
+ console.log(`${prefix} ${opName} attempt ${attemptNumber}/${opts.maxAttempts}`);
241
205
  }
242
206
  try {
243
207
  return await operation();
@@ -246,20 +210,26 @@ async function withRetry(operation, opName, opts) {
246
210
  if (opts.debug) {
247
211
  const msg = err instanceof Error ? err.message : String(err);
248
212
  console.warn(
249
- `[raindrop-ai/opencode-plugin] ${opName} attempt ${attempt} failed: ${msg}${attempt === opts.maxAttempts ? " (no more retries)" : ""}`
213
+ `${prefix} ${opName} attempt ${attemptNumber} failed: ${msg}${attemptNumber === opts.maxAttempts ? " (no more retries)" : ""}`
250
214
  );
251
215
  }
252
- if (attempt === opts.maxAttempts) break;
216
+ if (lastError && typeof lastError === "object" && "retryable" in lastError && !lastError.retryable)
217
+ break;
218
+ if (attemptNumber === opts.maxAttempts) break;
253
219
  }
254
220
  }
255
221
  throw lastError instanceof Error ? lastError : new Error(String(lastError));
256
222
  }
257
223
  async function postJson(url, body, headers, opts) {
224
+ const opName = `POST ${url}`;
258
225
  await withRetry(
259
226
  async () => {
260
227
  const resp = await fetch(url, {
261
228
  method: "POST",
262
- headers: { "Content-Type": "application/json", ...headers },
229
+ headers: {
230
+ "Content-Type": "application/json",
231
+ ...headers
232
+ },
263
233
  body: JSON.stringify(body)
264
234
  });
265
235
  if (!resp.ok) {
@@ -269,13 +239,71 @@ async function postJson(url, body, headers, opts) {
269
239
  );
270
240
  const retryAfterMs = parseRetryAfter(resp.headers);
271
241
  if (typeof retryAfterMs === "number") err.retryAfterMs = retryAfterMs;
242
+ err.retryable = resp.status === 429 || resp.status >= 500;
272
243
  throw err;
273
244
  }
274
245
  },
275
- `POST ${url}`,
246
+ opName,
276
247
  opts
277
248
  );
278
249
  }
250
+ var SpanStatusCode = {
251
+ UNSET: 0,
252
+ OK: 1,
253
+ ERROR: 2
254
+ };
255
+ function createSpanIds(parent) {
256
+ const traceId = parent ? parent.traceIdB64 : base64Encode(randomBytes(16));
257
+ const spanId = base64Encode(randomBytes(8));
258
+ return {
259
+ traceIdB64: traceId,
260
+ spanIdB64: spanId,
261
+ parentSpanIdB64: parent ? parent.spanIdB64 : void 0
262
+ };
263
+ }
264
+ function nowUnixNanoString() {
265
+ return Date.now().toString() + "000000";
266
+ }
267
+ function attrString(key, value) {
268
+ if (value === void 0) return void 0;
269
+ return { key, value: { stringValue: value } };
270
+ }
271
+ function attrInt(key, value) {
272
+ if (value === void 0) return void 0;
273
+ if (!Number.isFinite(value)) return void 0;
274
+ return { key, value: { intValue: String(Math.trunc(value)) } };
275
+ }
276
+ function buildOtlpSpan(args) {
277
+ const attrs = args.attributes.filter((x) => x !== void 0);
278
+ const span = {
279
+ traceId: args.ids.traceIdB64,
280
+ spanId: args.ids.spanIdB64,
281
+ name: args.name,
282
+ startTimeUnixNano: args.startTimeUnixNano,
283
+ endTimeUnixNano: args.endTimeUnixNano
284
+ };
285
+ if (args.ids.parentSpanIdB64) span.parentSpanId = args.ids.parentSpanIdB64;
286
+ if (attrs.length) span.attributes = attrs;
287
+ if (args.status) span.status = args.status;
288
+ return span;
289
+ }
290
+ function buildExportTraceServiceRequest(spans, serviceName = "raindrop.core", serviceVersion = "0.0.0") {
291
+ return {
292
+ resourceSpans: [
293
+ {
294
+ resource: {
295
+ attributes: [{ key: "service.name", value: { stringValue: serviceName } }]
296
+ },
297
+ scopeSpans: [
298
+ {
299
+ scope: { name: serviceName, version: serviceVersion },
300
+ spans
301
+ }
302
+ ]
303
+ }
304
+ ]
305
+ };
306
+ }
279
307
  function mergePatches(target, source) {
280
308
  var _a, _b, _c, _d;
281
309
  const out = { ...target, ...source };
@@ -293,34 +321,58 @@ var EventShipper = class {
293
321
  this.sticky = /* @__PURE__ */ new Map();
294
322
  this.timers = /* @__PURE__ */ new Map();
295
323
  this.inFlight = /* @__PURE__ */ new Set();
296
- var _a, _b;
297
- this.writeKey = opts.writeKey;
298
- this.baseUrl = (_a = formatEndpoint(opts.endpoint)) != null ? _a : "https://api.raindrop.ai/v1/";
324
+ var _a, _b, _c, _d, _e, _f, _g;
325
+ this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
326
+ this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
327
+ this.enabled = opts.enabled !== false;
299
328
  this.debug = opts.debug;
300
- this.partialFlushMs = (_b = opts.partialFlushMs) != null ? _b : 1e3;
329
+ this.partialFlushMs = (_c = opts.partialFlushMs) != null ? _c : 1e3;
330
+ this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
331
+ this.prefix = `[raindrop-ai/${this.sdkName}]`;
332
+ this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
333
+ const isNode = typeof process !== "undefined" && typeof process.version === "string";
334
+ this.context = {
335
+ library: {
336
+ name: (_f = opts.libraryName) != null ? _f : "@raindrop-ai/core",
337
+ version: (_g = opts.libraryVersion) != null ? _g : "0.0.0"
338
+ },
339
+ metadata: {
340
+ jsRuntime: isNode ? "node" : "web",
341
+ ...isNode ? { nodeVersion: process.version } : {}
342
+ }
343
+ };
344
+ }
345
+ isDebugEnabled() {
346
+ return this.debug;
347
+ }
348
+ authHeaders() {
349
+ return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
301
350
  }
302
351
  async patch(eventId, patch) {
303
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
304
- if (!eventId) return;
352
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
353
+ if (!this.enabled) return;
354
+ if (!eventId || !eventId.trim()) return;
305
355
  if (this.debug) {
306
- console.log("[raindrop-ai/opencode-plugin] queue patch", {
356
+ console.log(`${this.prefix} queue patch`, {
307
357
  eventId,
308
358
  userId: patch.userId,
359
+ eventName: patch.eventName,
309
360
  hasInput: typeof patch.input === "string" && patch.input.length > 0,
310
361
  hasOutput: typeof patch.output === "string" && patch.output.length > 0,
362
+ attachments: (_b = (_a = patch.attachments) == null ? void 0 : _a.length) != null ? _b : 0,
311
363
  isPending: patch.isPending
312
364
  });
313
365
  }
314
- const sticky = (_a = this.sticky.get(eventId)) != null ? _a : {};
315
- const existing = (_b = this.buffers.get(eventId)) != null ? _b : {};
366
+ const sticky = (_c = this.sticky.get(eventId)) != null ? _c : {};
367
+ const existing = (_d = this.buffers.get(eventId)) != null ? _d : {};
316
368
  const merged = mergePatches(existing, patch);
317
- merged.isPending = (_e = (_d = (_c = patch.isPending) != null ? _c : existing.isPending) != null ? _d : sticky.isPending) != null ? _e : true;
369
+ merged.isPending = (_g = (_f = (_e = patch.isPending) != null ? _e : existing.isPending) != null ? _f : sticky.isPending) != null ? _g : true;
318
370
  this.buffers.set(eventId, merged);
319
371
  this.sticky.set(eventId, {
320
- userId: (_f = merged.userId) != null ? _f : sticky.userId,
321
- convoId: (_g = merged.convoId) != null ? _g : sticky.convoId,
322
- eventName: (_h = merged.eventName) != null ? _h : sticky.eventName,
323
- isPending: (_i = merged.isPending) != null ? _i : sticky.isPending
372
+ userId: (_h = merged.userId) != null ? _h : sticky.userId,
373
+ convoId: (_i = merged.convoId) != null ? _i : sticky.convoId,
374
+ eventName: (_j = merged.eventName) != null ? _j : sticky.eventName,
375
+ isPending: (_k = merged.isPending) != null ? _k : sticky.isPending
324
376
  });
325
377
  const t = this.timers.get(eventId);
326
378
  if (t) clearTimeout(t);
@@ -338,6 +390,7 @@ var EventShipper = class {
338
390
  await this.patch(eventId, { ...patch, isPending: false });
339
391
  }
340
392
  async flush() {
393
+ if (!this.enabled) return;
341
394
  const ids = [...this.buffers.keys()];
342
395
  await Promise.all(ids.map((id) => this.flushOne(id)));
343
396
  await Promise.all([...this.inFlight].map((p) => p.catch(() => {
@@ -348,8 +401,70 @@ var EventShipper = class {
348
401
  this.timers.clear();
349
402
  await this.flush();
350
403
  }
404
+ async trackSignal(signal) {
405
+ var _a, _b;
406
+ if (!this.enabled) return;
407
+ const body = [
408
+ {
409
+ event_id: signal.eventId,
410
+ signal_name: signal.name,
411
+ signal_type: (_a = signal.type) != null ? _a : "default",
412
+ timestamp: signal.timestamp,
413
+ sentiment: signal.sentiment,
414
+ attachment_id: signal.attachmentId,
415
+ properties: {
416
+ ...(_b = signal.properties) != null ? _b : {},
417
+ ...signal.comment ? { comment: signal.comment } : {},
418
+ ...signal.after ? { after: signal.after } : {}
419
+ }
420
+ }
421
+ ];
422
+ const url = `${this.baseUrl}signals/track`;
423
+ try {
424
+ await postJson(url, body, this.authHeaders(), {
425
+ maxAttempts: 3,
426
+ debug: this.debug,
427
+ sdkName: this.sdkName
428
+ });
429
+ } catch (err) {
430
+ const msg = err instanceof Error ? err.message : String(err);
431
+ console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`);
432
+ }
433
+ }
434
+ async identify(users) {
435
+ if (!this.enabled) return;
436
+ const list = Array.isArray(users) ? users : [users];
437
+ const body = list.filter((user) => {
438
+ if (!(user == null ? void 0 : user.userId) || !user.userId.trim()) {
439
+ if (this.debug) {
440
+ console.warn(`${this.prefix} skipping identify: missing userId`);
441
+ }
442
+ return false;
443
+ }
444
+ return true;
445
+ }).map((user) => {
446
+ var _a;
447
+ return {
448
+ user_id: user.userId,
449
+ traits: (_a = user.traits) != null ? _a : {}
450
+ };
451
+ });
452
+ if (body.length === 0) return;
453
+ const url = `${this.baseUrl}users/identify`;
454
+ try {
455
+ await postJson(url, body, this.authHeaders(), {
456
+ maxAttempts: 3,
457
+ debug: this.debug,
458
+ sdkName: this.sdkName
459
+ });
460
+ } catch (err) {
461
+ const msg = err instanceof Error ? err.message : String(err);
462
+ console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`);
463
+ }
464
+ }
351
465
  async flushOne(eventId) {
352
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
466
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
467
+ if (!this.enabled) return;
353
468
  const timer = this.timers.get(eventId);
354
469
  if (timer) {
355
470
  clearTimeout(timer);
@@ -359,105 +474,123 @@ var EventShipper = class {
359
474
  this.buffers.delete(eventId);
360
475
  if (!accumulated) return;
361
476
  const sticky = (_a = this.sticky.get(eventId)) != null ? _a : {};
362
- const eventName = (_c = (_b = accumulated.eventName) != null ? _b : sticky.eventName) != null ? _c : "opencode_session";
477
+ const eventName = (_c = (_b = accumulated.eventName) != null ? _b : sticky.eventName) != null ? _c : this.defaultEventName;
363
478
  const userId = (_d = accumulated.userId) != null ? _d : sticky.userId;
364
479
  if (!userId) {
365
480
  if (this.debug) {
366
- console.warn(
367
- `[raindrop-ai/opencode-plugin] skipping track_partial for ${eventId}: missing userId`
368
- );
481
+ console.warn(`${this.prefix} skipping track_partial for ${eventId}: missing userId`);
369
482
  }
483
+ this.sticky.delete(eventId);
370
484
  return;
371
485
  }
486
+ const { wizardSession, ...restProperties } = (_e = accumulated.properties) != null ? _e : {};
372
487
  const payload = {
373
488
  event_id: eventId,
374
489
  user_id: userId,
375
490
  event: eventName,
376
- timestamp: (_e = accumulated.timestamp) != null ? _e : (/* @__PURE__ */ new Date()).toISOString(),
491
+ timestamp: (_f = accumulated.timestamp) != null ? _f : (/* @__PURE__ */ new Date()).toISOString(),
377
492
  ai_data: {
378
493
  input: accumulated.input,
379
494
  output: accumulated.output,
380
495
  model: accumulated.model,
381
- convo_id: (_f = accumulated.convoId) != null ? _f : sticky.convoId
496
+ convo_id: (_g = accumulated.convoId) != null ? _g : sticky.convoId
382
497
  },
383
498
  properties: {
384
- ...(_g = accumulated.properties) != null ? _g : {},
385
- $context: {
386
- library: { name: "@raindrop-ai/opencode-plugin", version: PLUGIN_VERSION },
387
- metadata: { jsRuntime: "node" }
388
- }
499
+ ...restProperties,
500
+ ...wizardSession ? { "raindrop.wizardSession": wizardSession } : {},
501
+ $context: this.context
389
502
  },
390
503
  attachments: accumulated.attachments,
391
504
  is_pending: ((_i = (_h = accumulated.isPending) != null ? _h : sticky.isPending) != null ? _i : true) !== false
392
505
  };
393
506
  const url = `${this.baseUrl}events/track_partial`;
394
507
  if (this.debug) {
395
- console.log("[raindrop-ai/opencode-plugin] sending track_partial", {
508
+ console.log(`${this.prefix} sending track_partial`, {
396
509
  eventId,
397
510
  eventName,
398
511
  userId,
399
- isPending: payload["is_pending"],
512
+ isPending: payload.is_pending,
400
513
  inputPreview: typeof accumulated.input === "string" ? accumulated.input.slice(0, 120) : void 0,
401
514
  outputPreview: typeof accumulated.output === "string" ? accumulated.output.slice(0, 120) : void 0,
515
+ attachments: (_k = (_j = accumulated.attachments) == null ? void 0 : _j.length) != null ? _k : 0,
516
+ attachmentKinds: (_m = (_l = accumulated.attachments) == null ? void 0 : _l.map((a) => ({
517
+ type: a.type,
518
+ role: a.role,
519
+ name: a.name,
520
+ valuePreview: a.value.slice(0, 60)
521
+ }))) != null ? _m : [],
402
522
  endpoint: url
403
523
  });
404
524
  }
405
- const p = postJson(
406
- url,
407
- payload,
408
- { Authorization: `Bearer ${this.writeKey}` },
409
- { maxAttempts: 3, debug: this.debug }
410
- );
525
+ const p = postJson(url, payload, this.authHeaders(), {
526
+ maxAttempts: 3,
527
+ debug: this.debug,
528
+ sdkName: this.sdkName
529
+ });
411
530
  this.inFlight.add(p);
412
531
  try {
413
532
  try {
414
533
  await p;
415
534
  if (this.debug) {
416
- console.log(`[raindrop-ai/opencode-plugin] sent track_partial ${eventId} (${eventName})`);
535
+ console.log(`${this.prefix} sent track_partial ${eventId} (${eventName})`);
417
536
  }
418
537
  } catch (err) {
419
- if (this.debug) {
420
- const msg = err instanceof Error ? err.message : String(err);
421
- console.warn(
422
- `[raindrop-ai/opencode-plugin] failed to send track_partial (dropping): ${msg}`
423
- );
424
- }
538
+ const msg = err instanceof Error ? err.message : String(err);
539
+ console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`);
425
540
  }
426
541
  } finally {
427
542
  this.inFlight.delete(p);
428
543
  }
429
- if (!payload["is_pending"]) this.sticky.delete(eventId);
544
+ const isPending = payload.is_pending;
545
+ if (!isPending) {
546
+ this.sticky.delete(eventId);
547
+ }
430
548
  }
431
549
  };
432
550
  var TraceShipper = class {
433
551
  constructor(opts) {
434
552
  this.queue = [];
435
553
  this.inFlight = /* @__PURE__ */ new Set();
436
- var _a, _b;
437
- this.writeKey = opts.writeKey;
438
- this.baseUrl = (_a = formatEndpoint(opts.endpoint)) != null ? _a : "https://api.raindrop.ai/v1/";
554
+ var _a, _b, _c, _d, _e, _f, _g, _h;
555
+ this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
556
+ this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
557
+ this.enabled = opts.enabled !== false;
439
558
  this.debug = opts.debug;
440
- this.flushIntervalMs = (_b = opts.flushIntervalMs) != null ? _b : 1e3;
441
- this.maxBatchSize = 50;
442
- this.maxQueueSize = 5e3;
559
+ this.debugSpans = opts.debugSpans === true;
560
+ this.flushIntervalMs = (_c = opts.flushIntervalMs) != null ? _c : 1e3;
561
+ this.maxBatchSize = (_d = opts.maxBatchSize) != null ? _d : 50;
562
+ this.maxQueueSize = (_e = opts.maxQueueSize) != null ? _e : 5e3;
563
+ this.sdkName = (_f = opts.sdkName) != null ? _f : "core";
564
+ this.prefix = `[raindrop-ai/${this.sdkName}]`;
565
+ this.serviceName = (_g = opts.serviceName) != null ? _g : "raindrop.core";
566
+ this.serviceVersion = (_h = opts.serviceVersion) != null ? _h : "0.0.0";
567
+ }
568
+ isDebugEnabled() {
569
+ return this.debug;
570
+ }
571
+ authHeaders() {
572
+ return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
443
573
  }
444
574
  startSpan(args) {
445
- var _a;
575
+ var _a, _b;
446
576
  const ids = createSpanIds(args.parent);
447
- const started = nowUnixNanoString();
577
+ const started = (_a = args.startTimeUnixNano) != null ? _a : nowUnixNanoString();
448
578
  const attrs = [
449
- attrString("ai.telemetry.metadata.raindrop.eventId", args.eventId)
579
+ attrString("ai.telemetry.metadata.raindrop.eventId", args.eventId),
580
+ attrString("ai.operationId", args.operationId)
450
581
  ];
451
- if ((_a = args.attributes) == null ? void 0 : _a.length) attrs.push(...args.attributes);
582
+ if ((_b = args.attributes) == null ? void 0 : _b.length) attrs.push(...args.attributes);
452
583
  return { ids, name: args.name, startTimeUnixNano: started, attributes: attrs };
453
584
  }
454
585
  endSpan(span, extra) {
455
- var _a;
586
+ var _a, _b;
456
587
  if (span.endTimeUnixNano) return;
457
- span.endTimeUnixNano = nowUnixNanoString();
458
- if ((_a = extra == null ? void 0 : extra.attributes) == null ? void 0 : _a.length) span.attributes.push(...extra.attributes);
459
- let status;
460
- if ((extra == null ? void 0 : extra.error) !== void 0) {
588
+ span.endTimeUnixNano = (_a = extra == null ? void 0 : extra.endTimeUnixNano) != null ? _a : nowUnixNanoString();
589
+ if ((_b = extra == null ? void 0 : extra.attributes) == null ? void 0 : _b.length) {
590
+ span.attributes.push(...extra.attributes);
591
+ }
592
+ let status = extra == null ? void 0 : extra.status;
593
+ if (!status && (extra == null ? void 0 : extra.error) !== void 0) {
461
594
  const message = extra.error instanceof Error ? extra.error.message : String(extra.error);
462
595
  status = { code: SpanStatusCode.ERROR, message };
463
596
  }
@@ -471,10 +604,6 @@ var TraceShipper = class {
471
604
  });
472
605
  this.enqueue(otlp);
473
606
  }
474
- /**
475
- * Create and enqueue a span with both start and end times (atomic insert).
476
- * Used for tool spans and any span whose full duration is known at write time.
477
- */
478
607
  createSpan(args) {
479
608
  var _a;
480
609
  const ids = createSpanIds(args.parent);
@@ -493,7 +622,18 @@ var TraceShipper = class {
493
622
  this.enqueue(otlp);
494
623
  }
495
624
  enqueue(span) {
496
- if (this.queue.length >= this.maxQueueSize) this.queue.shift();
625
+ if (!this.enabled) return;
626
+ if (this.debugSpans) {
627
+ const short = (s) => s ? s.slice(-8) : "none";
628
+ console.log(
629
+ `${this.prefix}[span] name=${span.name} trace=${short(span.traceId)} span=${short(span.spanId)} parent=${short(
630
+ span.parentSpanId
631
+ )}`
632
+ );
633
+ }
634
+ if (this.queue.length >= this.maxQueueSize) {
635
+ this.queue.shift();
636
+ }
497
637
  this.queue.push(span);
498
638
  if (this.queue.length >= this.maxBatchSize) {
499
639
  void this.flush().catch(() => {
@@ -509,42 +649,34 @@ var TraceShipper = class {
509
649
  }
510
650
  }
511
651
  async flush() {
652
+ if (!this.enabled) return;
512
653
  if (this.timer) {
513
654
  clearTimeout(this.timer);
514
655
  this.timer = void 0;
515
656
  }
516
657
  while (this.queue.length > 0) {
517
658
  const batch = this.queue.splice(0, this.maxBatchSize);
518
- const body = buildExportTraceServiceRequest(batch);
659
+ const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
519
660
  const url = `${this.baseUrl}traces`;
520
- const spanNames = batch.map((span) => span.name);
521
661
  if (this.debug) {
522
- console.log("[raindrop-ai/opencode-plugin] sending traces batch", {
662
+ console.log(`${this.prefix} sending traces batch`, {
523
663
  spans: batch.length,
524
- endpoint: url,
525
- spanNames
664
+ endpoint: url
526
665
  });
527
666
  }
528
- const p = postJson(
529
- url,
530
- body,
531
- { Authorization: `Bearer ${this.writeKey}` },
532
- { maxAttempts: 3, debug: this.debug }
533
- );
667
+ const p = postJson(url, body, this.authHeaders(), {
668
+ maxAttempts: 3,
669
+ debug: this.debug,
670
+ sdkName: this.sdkName
671
+ });
534
672
  this.inFlight.add(p);
535
673
  try {
536
674
  try {
537
675
  await p;
538
- if (this.debug) {
539
- console.log(`[raindrop-ai/opencode-plugin] sent ${batch.length} spans`, {
540
- spanNames
541
- });
542
- }
676
+ if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
543
677
  } catch (err) {
544
- if (this.debug) {
545
- const msg = err instanceof Error ? err.message : String(err);
546
- console.warn(`[raindrop-ai/opencode-plugin] failed to send spans (dropping): ${msg}`);
547
- }
678
+ const msg = err instanceof Error ? err.message : String(err);
679
+ console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`);
548
680
  }
549
681
  } finally {
550
682
  this.inFlight.delete(p);
@@ -562,6 +694,41 @@ var TraceShipper = class {
562
694
  }
563
695
  };
564
696
 
697
+ // src/shipper.ts
698
+ var EventShipper2 = class extends EventShipper {
699
+ constructor(opts) {
700
+ var _a, _b, _c, _d;
701
+ super({
702
+ ...opts,
703
+ sdkName: (_a = opts.sdkName) != null ? _a : "opencode-plugin",
704
+ libraryName: (_b = opts.libraryName) != null ? _b : PLUGIN_NAME,
705
+ libraryVersion: (_c = opts.libraryVersion) != null ? _c : PLUGIN_VERSION,
706
+ defaultEventName: (_d = opts.defaultEventName) != null ? _d : "opencode_session"
707
+ });
708
+ }
709
+ };
710
+ var TraceShipper2 = class extends TraceShipper {
711
+ constructor(opts) {
712
+ var _a, _b, _c;
713
+ super({
714
+ ...opts,
715
+ sdkName: (_a = opts.sdkName) != null ? _a : "opencode-plugin",
716
+ serviceName: (_b = opts.serviceName) != null ? _b : "raindrop.opencode-plugin",
717
+ serviceVersion: (_c = opts.serviceVersion) != null ? _c : PLUGIN_VERSION
718
+ });
719
+ }
720
+ enqueue(span) {
721
+ var _a;
722
+ const attrs = (_a = span.attributes) != null ? _a : [];
723
+ attrs.unshift(
724
+ { key: "span.id", value: { stringValue: span.spanId } },
725
+ ...span.parentSpanId ? [{ key: "span.parent.id", value: { stringValue: span.parentSpanId } }] : []
726
+ );
727
+ span.attributes = attrs;
728
+ super.enqueue(span);
729
+ }
730
+ };
731
+
565
732
  // src/tracing-linkage-helpers.ts
566
733
  function createSessionParentMapHelpers({
567
734
  sessions: sessions2,
@@ -647,8 +814,6 @@ var pendingChildSessionsByCallKey = /* @__PURE__ */ new Map();
647
814
  function createSessionState(sessionId) {
648
815
  return {
649
816
  sessionId,
650
- turnNumber: 0,
651
- toolCount: 0,
652
817
  currentInput: "",
653
818
  outputParts: /* @__PURE__ */ new Map(),
654
819
  reasoningParts: /* @__PURE__ */ new Map(),
@@ -702,7 +867,7 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
702
867
  // event — session lifecycle + streaming parts
703
868
  // ------------------------------------------------------------------
704
869
  event: async ({ event }) => {
705
- var _a, _b, _c, _d, _e;
870
+ var _a, _b, _c, _d, _e, _f, _g;
706
871
  try {
707
872
  const props = event.properties;
708
873
  const info = props["info"];
@@ -920,12 +1085,11 @@ type: ${name != null ? name : "UnknownError"}`;
920
1085
  }
921
1086
  if (!state.parentId) {
922
1087
  await eventShipper.finish(state.currentEventId, {
923
- userId: state.sessionId,
1088
+ userId: (_d = (_c = state.eventMetadata) == null ? void 0 : _c.userId) != null ? _d : state.sessionId,
924
1089
  model: modelName,
925
1090
  output: outputText,
926
1091
  properties: {
927
- turn_count: state.turnNumber,
928
- tool_count: state.toolCount
1092
+ plugin_version: PLUGIN_VERSION
929
1093
  }
930
1094
  });
931
1095
  }
@@ -963,7 +1127,7 @@ type: ${name != null ? name : "UnknownError"}`;
963
1127
  await Promise.all([eventShipper.flush(), traceShipper.flush()]);
964
1128
  sessions.delete(String(sessionID));
965
1129
  } else if (event.type === "session.error") {
966
- const errorSessionID = (_c = props["sessionID"]) != null ? _c : sessionID;
1130
+ const errorSessionID = (_e = props["sessionID"]) != null ? _e : sessionID;
967
1131
  if (errorSessionID === void 0 || errorSessionID === "")
968
1132
  throw new Error("session.error: missing sessionID");
969
1133
  const state = sessions.get(String(errorSessionID));
@@ -971,7 +1135,7 @@ type: ${name != null ? name : "UnknownError"}`;
971
1135
  const errorObj = props["error"];
972
1136
  if (errorObj == null) throw new Error("session.error: missing error object");
973
1137
  const errorName = errorObj.name;
974
- const errorMessage = (_e = (_d = errorObj.data) == null ? void 0 : _d.message) != null ? _e : errorObj.name;
1138
+ const errorMessage = (_g = (_f = errorObj.data) == null ? void 0 : _f.message) != null ? _g : errorObj.name;
975
1139
  if (errorName == null && errorMessage == null)
976
1140
  throw new Error("session.error: error object must have name or data.message");
977
1141
  const errorStr = `${errorMessage != null ? errorMessage : errorName}
@@ -998,6 +1162,7 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
998
1162
  // chat.message — user sent a message; start a new turn with a fresh event ID
999
1163
  // ------------------------------------------------------------------
1000
1164
  "chat.message": async (messageInput, output) => {
1165
+ var _a, _b, _c, _d, _e, _f, _g;
1001
1166
  try {
1002
1167
  const { sessionID } = messageInput;
1003
1168
  let state = sessions.get(sessionID);
@@ -1010,15 +1175,24 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
1010
1175
  traceShipper.endSpan(state.currentRootSpan);
1011
1176
  state.currentRootSpan = void 0;
1012
1177
  }
1013
- state.turnNumber++;
1014
1178
  if (output == null) throw new Error("chat.message: output is required");
1015
1179
  const parts = output.parts;
1016
1180
  if (parts == null) throw new Error("chat.message: output.parts is required");
1017
1181
  const textParts = [];
1018
1182
  const newAttachments = [];
1183
+ const baseMeta = { ...config.eventMetadata };
1184
+ const promptMeta = {};
1019
1185
  for (const part of parts) {
1020
1186
  if (part["type"] === "text" && typeof part["text"] === "string") {
1021
1187
  textParts.push(part["text"]);
1188
+ const meta = part["metadata"];
1189
+ if (meta != null && typeof meta === "object") {
1190
+ const m = meta;
1191
+ if (typeof m["userId"] === "string") promptMeta.userId = m["userId"];
1192
+ if (typeof m["eventName"] === "string") promptMeta.eventName = m["eventName"];
1193
+ if (m["properties"] != null && typeof m["properties"] === "object")
1194
+ promptMeta.properties = m["properties"];
1195
+ }
1022
1196
  } else if (part["type"] === "file") {
1023
1197
  const mimeType = part["mediaType"];
1024
1198
  const filename = part["filename"];
@@ -1030,6 +1204,11 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
1030
1204
  }
1031
1205
  }
1032
1206
  }
1207
+ state.eventMetadata = {
1208
+ ...baseMeta,
1209
+ ...promptMeta,
1210
+ properties: baseMeta.properties || promptMeta.properties ? { ...(_a = baseMeta.properties) != null ? _a : {}, ...(_b = promptMeta.properties) != null ? _b : {} } : void 0
1211
+ };
1033
1212
  const userText = textParts.join("\n");
1034
1213
  state.currentInput = userText;
1035
1214
  if (state.parentId) {
@@ -1065,9 +1244,9 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
1065
1244
  });
1066
1245
  await eventShipper.patch(state.currentEventId, {
1067
1246
  isPending: true,
1068
- userId: state.sessionId,
1247
+ userId: (_d = (_c = state.eventMetadata) == null ? void 0 : _c.userId) != null ? _d : state.sessionId,
1069
1248
  convoId: state.sessionId,
1070
- eventName: config.eventName,
1249
+ eventName: (_f = (_e = state.eventMetadata) == null ? void 0 : _e.eventName) != null ? _f : config.eventName,
1071
1250
  input: userText,
1072
1251
  ...newAttachments.length > 0 ? { attachments: newAttachments } : {},
1073
1252
  properties: {
@@ -1075,8 +1254,8 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
1075
1254
  directory,
1076
1255
  hostname,
1077
1256
  os,
1078
- turn_count: state.turnNumber,
1079
- tool_count: state.toolCount
1257
+ plugin_version: PLUGIN_VERSION,
1258
+ ...(_g = state.eventMetadata) == null ? void 0 : _g.properties
1080
1259
  }
1081
1260
  });
1082
1261
  } catch (err) {
@@ -1087,7 +1266,7 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
1087
1266
  // For child sessions, tool spans are parented directly to the parent task span.
1088
1267
  // ------------------------------------------------------------------
1089
1268
  "tool.execute.before": async (toolInput, _output) => {
1090
- var _a;
1269
+ var _a, _b, _c;
1091
1270
  try {
1092
1271
  const { tool, sessionID, callID } = toolInput;
1093
1272
  const state = sessions.get(sessionID);
@@ -1111,7 +1290,7 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
1111
1290
  ids: liveTaskSpan.ids,
1112
1291
  eventContext: {
1113
1292
  eventId: state.currentEventId,
1114
- userId: state.sessionId,
1293
+ userId: (_c = (_b = state.eventMetadata) == null ? void 0 : _b.userId) != null ? _c : state.sessionId,
1115
1294
  convoId: state.sessionId
1116
1295
  }
1117
1296
  };
@@ -1152,7 +1331,6 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
1152
1331
  const { tool, sessionID, callID, args } = toolInput;
1153
1332
  const state = sessions.get(sessionID);
1154
1333
  if (!state || !state.currentEventId) return;
1155
- state.toolCount++;
1156
1334
  if (args === void 0 || args === null)
1157
1335
  throw new Error("tool.execute.after: args is required");
1158
1336
  const toolCallArgs = typeof args === "string" ? args : JSON.stringify(args);
@@ -1206,12 +1384,6 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
1206
1384
  if (running.size === 0) runningTaskCallsBySession.delete(sessionID);
1207
1385
  }
1208
1386
  }
1209
- if (!state.parentId) {
1210
- await eventShipper.patch(state.currentEventId, {
1211
- userId: state.sessionId,
1212
- properties: { tool_count: state.toolCount }
1213
- });
1214
- }
1215
1387
  } catch (err) {
1216
1388
  console.log(
1217
1389
  `[raindrop-ai/opencode-plugin] [error] Error in tool.execute.after hook: ${err instanceof Error ? err.message : String(err)}`
@@ -1228,6 +1400,7 @@ async function plugin(input) {
1228
1400
  function appLog(level, message) {
1229
1401
  console.log(`[raindrop-ai/opencode-plugin] [${level}] ${message}`);
1230
1402
  }
1403
+ appLog("info", `Loading ${PLUGIN_NAME} v${PLUGIN_VERSION}`);
1231
1404
  if (!config.writeKey) {
1232
1405
  appLog(
1233
1406
  "warn",
@@ -1238,12 +1411,12 @@ async function plugin(input) {
1238
1411
  if (config.debug) {
1239
1412
  appLog("info", `Raindrop tracing enabled (endpoint: ${config.endpoint})`);
1240
1413
  }
1241
- const eventShipper = new EventShipper({
1414
+ const eventShipper = new EventShipper2({
1242
1415
  writeKey: config.writeKey,
1243
1416
  endpoint: config.endpoint,
1244
1417
  debug: config.debug
1245
1418
  });
1246
- const traceShipper = new TraceShipper({
1419
+ const traceShipper = new TraceShipper2({
1247
1420
  writeKey: config.writeKey,
1248
1421
  endpoint: config.endpoint,
1249
1422
  debug: config.debug