@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.cjs CHANGED
@@ -45,18 +45,27 @@ function loadConfig(projectDirectory) {
45
45
  } catch (e) {
46
46
  }
47
47
  }
48
+ let eventMetadata;
49
+ const envMeta = process.env["RAINDROP_EVENT_METADATA"];
50
+ if (envMeta) {
51
+ try {
52
+ eventMetadata = JSON.parse(envMeta);
53
+ } catch (e) {
54
+ }
55
+ }
48
56
  return {
49
57
  writeKey: (_b = (_a = process.env["RAINDROP_WRITE_KEY"]) != null ? _a : merged.write_key) != null ? _b : "",
50
58
  endpoint: (_d = (_c = process.env["RAINDROP_API_URL"]) != null ? _c : merged.api_url) != null ? _d : "https://api.raindrop.ai/v1",
51
59
  eventName: (_e = merged.event_name) != null ? _e : "opencode_session",
52
- debug: process.env["RAINDROP_DEBUG"] === "true" ? true : (_f = merged.debug) != null ? _f : false
60
+ debug: process.env["RAINDROP_DEBUG"] === "true" ? true : (_f = merged.debug) != null ? _f : false,
61
+ eventMetadata
53
62
  };
54
63
  }
55
64
 
56
65
  // package.json
57
66
  var package_default = {
58
67
  name: "@raindrop-ai/opencode-plugin",
59
- version: "0.0.4",
68
+ version: "0.0.6",
60
69
  description: "Raindrop observability plugin for OpenCode \u2014 automatic session/event/span tracing",
61
70
  type: "module",
62
71
  main: "dist/index.cjs",
@@ -77,7 +86,7 @@ var package_default = {
77
86
  build: "tsup",
78
87
  dev: "tsup --watch",
79
88
  clean: "rm -rf dist",
80
- test: "cd tests && yarn test"
89
+ test: "cd tests && pnpm test"
81
90
  },
82
91
  peerDependencies: {
83
92
  "@opencode-ai/plugin": ">=1.2.0",
@@ -89,6 +98,7 @@ var package_default = {
89
98
  }
90
99
  },
91
100
  devDependencies: {
101
+ "@raindrop-ai/core": "workspace:*",
92
102
  "@opencode-ai/plugin": "^1.2.0",
93
103
  "@opencode-ai/sdk": "^1.2.0",
94
104
  "@types/node": "^20.11.17",
@@ -106,33 +116,44 @@ var package_default = {
106
116
  dts: {
107
117
  resolve: true
108
118
  },
109
- clean: true
119
+ clean: true,
120
+ noExternal: [
121
+ "@raindrop-ai/core"
122
+ ]
110
123
  },
111
124
  publishConfig: {
112
125
  access: "public"
113
126
  }
114
127
  };
115
128
 
116
- // src/shipper.ts
129
+ // src/package-info.ts
130
+ var PLUGIN_NAME = package_default.name;
117
131
  var PLUGIN_VERSION = package_default.version;
118
- function getCryptoObj() {
119
- return globalThis.crypto;
132
+
133
+ // ../core/dist/chunk-FTBZHS25.js
134
+ function getCrypto() {
135
+ const c = globalThis.crypto;
136
+ return c;
120
137
  }
121
138
  function randomBytes(length) {
122
- const c = getCryptoObj();
139
+ const cryptoObj = getCrypto();
123
140
  const out = new Uint8Array(length);
124
- if (c == null ? void 0 : c.getRandomValues) {
125
- c.getRandomValues(out);
141
+ if (cryptoObj && typeof cryptoObj.getRandomValues === "function") {
142
+ cryptoObj.getRandomValues(out);
126
143
  return out;
127
144
  }
128
145
  for (let i = 0; i < out.length; i++) out[i] = Math.floor(Math.random() * 256);
129
146
  return out;
130
147
  }
131
148
  function base64Encode(bytes) {
132
- const buf = globalThis.Buffer;
133
- if (buf) return buf.from(bytes).toString("base64");
149
+ const maybeBuffer = globalThis.Buffer;
150
+ if (maybeBuffer) {
151
+ return maybeBuffer.from(bytes).toString("base64");
152
+ }
134
153
  let binary = "";
135
- for (let i2 = 0; i2 < bytes.length; i2++) binary += String.fromCharCode(bytes[i2]);
154
+ for (let i2 = 0; i2 < bytes.length; i2++) {
155
+ binary += String.fromCharCode(bytes[i2]);
156
+ }
136
157
  const btoaFn = globalThis.btoa;
137
158
  if (typeof btoaFn === "function") return btoaFn(binary);
138
159
  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
@@ -153,69 +174,9 @@ function base64Encode(bytes) {
153
174
  }
154
175
  return out;
155
176
  }
156
- var SERVICE_NAME = "raindrop.opencode-plugin";
157
- var SpanStatusCode = {
158
- ERROR: 2
159
- };
160
177
  function generateId() {
161
178
  return base64Encode(randomBytes(12)).replace(/[+/=]/g, "").slice(0, 16);
162
179
  }
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
180
  function wait(ms) {
220
181
  return new Promise((resolve) => setTimeout(resolve, ms));
221
182
  }
@@ -228,7 +189,7 @@ function parseRetryAfter(headers) {
228
189
  const value = (_a = headers.get("Retry-After")) != null ? _a : headers.get("retry-after");
229
190
  if (!value) return void 0;
230
191
  const asNumber = Number(value);
231
- if (!Number.isNaN(asNumber)) return asNumber * 1e3;
192
+ if (value.trim() !== "" && !Number.isNaN(asNumber)) return asNumber * 1e3;
232
193
  const asDate = new Date(value).getTime();
233
194
  if (!Number.isNaN(asDate)) {
234
195
  const delta = asDate - Date.now();
@@ -237,26 +198,29 @@ function parseRetryAfter(headers) {
237
198
  return void 0;
238
199
  }
239
200
  function getRetryDelayMs(attemptNumber, previousError) {
240
- if (previousError !== null && typeof previousError === "object" && "retryAfterMs" in previousError) {
201
+ if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
241
202
  const v = previousError.retryAfterMs;
242
203
  if (typeof v === "number") return Math.max(0, v);
243
204
  }
244
205
  if (attemptNumber <= 1) return 0;
245
- return 500 * Math.pow(2, attemptNumber - 2);
206
+ const base = 500;
207
+ const factor = Math.pow(2, attemptNumber - 2);
208
+ return base * factor;
246
209
  }
247
210
  async function withRetry(operation, opName, opts) {
211
+ const prefix = opts.sdkName ? `[raindrop-ai/${opts.sdkName}]` : "[raindrop-ai/core]";
248
212
  let lastError = void 0;
249
- for (let attempt = 1; attempt <= opts.maxAttempts; attempt++) {
250
- if (attempt > 1) {
251
- const delay = getRetryDelayMs(attempt, lastError);
213
+ for (let attemptNumber = 1; attemptNumber <= opts.maxAttempts; attemptNumber++) {
214
+ if (attemptNumber > 1) {
215
+ const delay = getRetryDelayMs(attemptNumber, lastError);
252
216
  if (opts.debug) {
253
217
  console.warn(
254
- `[raindrop-ai/opencode-plugin] ${opName} retry ${attempt}/${opts.maxAttempts} in ${delay}ms`
218
+ `${prefix} ${opName} retry ${attemptNumber}/${opts.maxAttempts} in ${delay}ms`
255
219
  );
256
220
  }
257
221
  if (delay > 0) await wait(delay);
258
222
  } else if (opts.debug) {
259
- console.log(`[raindrop-ai/opencode-plugin] ${opName} attempt ${attempt}/${opts.maxAttempts}`);
223
+ console.log(`${prefix} ${opName} attempt ${attemptNumber}/${opts.maxAttempts}`);
260
224
  }
261
225
  try {
262
226
  return await operation();
@@ -265,20 +229,26 @@ async function withRetry(operation, opName, opts) {
265
229
  if (opts.debug) {
266
230
  const msg = err instanceof Error ? err.message : String(err);
267
231
  console.warn(
268
- `[raindrop-ai/opencode-plugin] ${opName} attempt ${attempt} failed: ${msg}${attempt === opts.maxAttempts ? " (no more retries)" : ""}`
232
+ `${prefix} ${opName} attempt ${attemptNumber} failed: ${msg}${attemptNumber === opts.maxAttempts ? " (no more retries)" : ""}`
269
233
  );
270
234
  }
271
- if (attempt === opts.maxAttempts) break;
235
+ if (lastError && typeof lastError === "object" && "retryable" in lastError && !lastError.retryable)
236
+ break;
237
+ if (attemptNumber === opts.maxAttempts) break;
272
238
  }
273
239
  }
274
240
  throw lastError instanceof Error ? lastError : new Error(String(lastError));
275
241
  }
276
242
  async function postJson(url, body, headers, opts) {
243
+ const opName = `POST ${url}`;
277
244
  await withRetry(
278
245
  async () => {
279
246
  const resp = await fetch(url, {
280
247
  method: "POST",
281
- headers: { "Content-Type": "application/json", ...headers },
248
+ headers: {
249
+ "Content-Type": "application/json",
250
+ ...headers
251
+ },
282
252
  body: JSON.stringify(body)
283
253
  });
284
254
  if (!resp.ok) {
@@ -288,13 +258,71 @@ async function postJson(url, body, headers, opts) {
288
258
  );
289
259
  const retryAfterMs = parseRetryAfter(resp.headers);
290
260
  if (typeof retryAfterMs === "number") err.retryAfterMs = retryAfterMs;
261
+ err.retryable = resp.status === 429 || resp.status >= 500;
291
262
  throw err;
292
263
  }
293
264
  },
294
- `POST ${url}`,
265
+ opName,
295
266
  opts
296
267
  );
297
268
  }
269
+ var SpanStatusCode = {
270
+ UNSET: 0,
271
+ OK: 1,
272
+ ERROR: 2
273
+ };
274
+ function createSpanIds(parent) {
275
+ const traceId = parent ? parent.traceIdB64 : base64Encode(randomBytes(16));
276
+ const spanId = base64Encode(randomBytes(8));
277
+ return {
278
+ traceIdB64: traceId,
279
+ spanIdB64: spanId,
280
+ parentSpanIdB64: parent ? parent.spanIdB64 : void 0
281
+ };
282
+ }
283
+ function nowUnixNanoString() {
284
+ return Date.now().toString() + "000000";
285
+ }
286
+ function attrString(key, value) {
287
+ if (value === void 0) return void 0;
288
+ return { key, value: { stringValue: value } };
289
+ }
290
+ function attrInt(key, value) {
291
+ if (value === void 0) return void 0;
292
+ if (!Number.isFinite(value)) return void 0;
293
+ return { key, value: { intValue: String(Math.trunc(value)) } };
294
+ }
295
+ function buildOtlpSpan(args) {
296
+ const attrs = args.attributes.filter((x) => x !== void 0);
297
+ const span = {
298
+ traceId: args.ids.traceIdB64,
299
+ spanId: args.ids.spanIdB64,
300
+ name: args.name,
301
+ startTimeUnixNano: args.startTimeUnixNano,
302
+ endTimeUnixNano: args.endTimeUnixNano
303
+ };
304
+ if (args.ids.parentSpanIdB64) span.parentSpanId = args.ids.parentSpanIdB64;
305
+ if (attrs.length) span.attributes = attrs;
306
+ if (args.status) span.status = args.status;
307
+ return span;
308
+ }
309
+ function buildExportTraceServiceRequest(spans, serviceName = "raindrop.core", serviceVersion = "0.0.0") {
310
+ return {
311
+ resourceSpans: [
312
+ {
313
+ resource: {
314
+ attributes: [{ key: "service.name", value: { stringValue: serviceName } }]
315
+ },
316
+ scopeSpans: [
317
+ {
318
+ scope: { name: serviceName, version: serviceVersion },
319
+ spans
320
+ }
321
+ ]
322
+ }
323
+ ]
324
+ };
325
+ }
298
326
  function mergePatches(target, source) {
299
327
  var _a, _b, _c, _d;
300
328
  const out = { ...target, ...source };
@@ -312,34 +340,58 @@ var EventShipper = class {
312
340
  this.sticky = /* @__PURE__ */ new Map();
313
341
  this.timers = /* @__PURE__ */ new Map();
314
342
  this.inFlight = /* @__PURE__ */ new Set();
315
- var _a, _b;
316
- this.writeKey = opts.writeKey;
317
- this.baseUrl = (_a = formatEndpoint(opts.endpoint)) != null ? _a : "https://api.raindrop.ai/v1/";
343
+ var _a, _b, _c, _d, _e, _f, _g;
344
+ this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
345
+ this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
346
+ this.enabled = opts.enabled !== false;
318
347
  this.debug = opts.debug;
319
- this.partialFlushMs = (_b = opts.partialFlushMs) != null ? _b : 1e3;
348
+ this.partialFlushMs = (_c = opts.partialFlushMs) != null ? _c : 1e3;
349
+ this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
350
+ this.prefix = `[raindrop-ai/${this.sdkName}]`;
351
+ this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
352
+ const isNode = typeof process !== "undefined" && typeof process.version === "string";
353
+ this.context = {
354
+ library: {
355
+ name: (_f = opts.libraryName) != null ? _f : "@raindrop-ai/core",
356
+ version: (_g = opts.libraryVersion) != null ? _g : "0.0.0"
357
+ },
358
+ metadata: {
359
+ jsRuntime: isNode ? "node" : "web",
360
+ ...isNode ? { nodeVersion: process.version } : {}
361
+ }
362
+ };
363
+ }
364
+ isDebugEnabled() {
365
+ return this.debug;
366
+ }
367
+ authHeaders() {
368
+ return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
320
369
  }
321
370
  async patch(eventId, patch) {
322
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
323
- if (!eventId) return;
371
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
372
+ if (!this.enabled) return;
373
+ if (!eventId || !eventId.trim()) return;
324
374
  if (this.debug) {
325
- console.log("[raindrop-ai/opencode-plugin] queue patch", {
375
+ console.log(`${this.prefix} queue patch`, {
326
376
  eventId,
327
377
  userId: patch.userId,
378
+ eventName: patch.eventName,
328
379
  hasInput: typeof patch.input === "string" && patch.input.length > 0,
329
380
  hasOutput: typeof patch.output === "string" && patch.output.length > 0,
381
+ attachments: (_b = (_a = patch.attachments) == null ? void 0 : _a.length) != null ? _b : 0,
330
382
  isPending: patch.isPending
331
383
  });
332
384
  }
333
- const sticky = (_a = this.sticky.get(eventId)) != null ? _a : {};
334
- const existing = (_b = this.buffers.get(eventId)) != null ? _b : {};
385
+ const sticky = (_c = this.sticky.get(eventId)) != null ? _c : {};
386
+ const existing = (_d = this.buffers.get(eventId)) != null ? _d : {};
335
387
  const merged = mergePatches(existing, patch);
336
- merged.isPending = (_e = (_d = (_c = patch.isPending) != null ? _c : existing.isPending) != null ? _d : sticky.isPending) != null ? _e : true;
388
+ merged.isPending = (_g = (_f = (_e = patch.isPending) != null ? _e : existing.isPending) != null ? _f : sticky.isPending) != null ? _g : true;
337
389
  this.buffers.set(eventId, merged);
338
390
  this.sticky.set(eventId, {
339
- userId: (_f = merged.userId) != null ? _f : sticky.userId,
340
- convoId: (_g = merged.convoId) != null ? _g : sticky.convoId,
341
- eventName: (_h = merged.eventName) != null ? _h : sticky.eventName,
342
- isPending: (_i = merged.isPending) != null ? _i : sticky.isPending
391
+ userId: (_h = merged.userId) != null ? _h : sticky.userId,
392
+ convoId: (_i = merged.convoId) != null ? _i : sticky.convoId,
393
+ eventName: (_j = merged.eventName) != null ? _j : sticky.eventName,
394
+ isPending: (_k = merged.isPending) != null ? _k : sticky.isPending
343
395
  });
344
396
  const t = this.timers.get(eventId);
345
397
  if (t) clearTimeout(t);
@@ -357,6 +409,7 @@ var EventShipper = class {
357
409
  await this.patch(eventId, { ...patch, isPending: false });
358
410
  }
359
411
  async flush() {
412
+ if (!this.enabled) return;
360
413
  const ids = [...this.buffers.keys()];
361
414
  await Promise.all(ids.map((id) => this.flushOne(id)));
362
415
  await Promise.all([...this.inFlight].map((p) => p.catch(() => {
@@ -367,8 +420,70 @@ var EventShipper = class {
367
420
  this.timers.clear();
368
421
  await this.flush();
369
422
  }
423
+ async trackSignal(signal) {
424
+ var _a, _b;
425
+ if (!this.enabled) return;
426
+ const body = [
427
+ {
428
+ event_id: signal.eventId,
429
+ signal_name: signal.name,
430
+ signal_type: (_a = signal.type) != null ? _a : "default",
431
+ timestamp: signal.timestamp,
432
+ sentiment: signal.sentiment,
433
+ attachment_id: signal.attachmentId,
434
+ properties: {
435
+ ...(_b = signal.properties) != null ? _b : {},
436
+ ...signal.comment ? { comment: signal.comment } : {},
437
+ ...signal.after ? { after: signal.after } : {}
438
+ }
439
+ }
440
+ ];
441
+ const url = `${this.baseUrl}signals/track`;
442
+ try {
443
+ await postJson(url, body, this.authHeaders(), {
444
+ maxAttempts: 3,
445
+ debug: this.debug,
446
+ sdkName: this.sdkName
447
+ });
448
+ } catch (err) {
449
+ const msg = err instanceof Error ? err.message : String(err);
450
+ console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`);
451
+ }
452
+ }
453
+ async identify(users) {
454
+ if (!this.enabled) return;
455
+ const list = Array.isArray(users) ? users : [users];
456
+ const body = list.filter((user) => {
457
+ if (!(user == null ? void 0 : user.userId) || !user.userId.trim()) {
458
+ if (this.debug) {
459
+ console.warn(`${this.prefix} skipping identify: missing userId`);
460
+ }
461
+ return false;
462
+ }
463
+ return true;
464
+ }).map((user) => {
465
+ var _a;
466
+ return {
467
+ user_id: user.userId,
468
+ traits: (_a = user.traits) != null ? _a : {}
469
+ };
470
+ });
471
+ if (body.length === 0) return;
472
+ const url = `${this.baseUrl}users/identify`;
473
+ try {
474
+ await postJson(url, body, this.authHeaders(), {
475
+ maxAttempts: 3,
476
+ debug: this.debug,
477
+ sdkName: this.sdkName
478
+ });
479
+ } catch (err) {
480
+ const msg = err instanceof Error ? err.message : String(err);
481
+ console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`);
482
+ }
483
+ }
370
484
  async flushOne(eventId) {
371
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
485
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
486
+ if (!this.enabled) return;
372
487
  const timer = this.timers.get(eventId);
373
488
  if (timer) {
374
489
  clearTimeout(timer);
@@ -378,105 +493,123 @@ var EventShipper = class {
378
493
  this.buffers.delete(eventId);
379
494
  if (!accumulated) return;
380
495
  const sticky = (_a = this.sticky.get(eventId)) != null ? _a : {};
381
- const eventName = (_c = (_b = accumulated.eventName) != null ? _b : sticky.eventName) != null ? _c : "opencode_session";
496
+ const eventName = (_c = (_b = accumulated.eventName) != null ? _b : sticky.eventName) != null ? _c : this.defaultEventName;
382
497
  const userId = (_d = accumulated.userId) != null ? _d : sticky.userId;
383
498
  if (!userId) {
384
499
  if (this.debug) {
385
- console.warn(
386
- `[raindrop-ai/opencode-plugin] skipping track_partial for ${eventId}: missing userId`
387
- );
500
+ console.warn(`${this.prefix} skipping track_partial for ${eventId}: missing userId`);
388
501
  }
502
+ this.sticky.delete(eventId);
389
503
  return;
390
504
  }
505
+ const { wizardSession, ...restProperties } = (_e = accumulated.properties) != null ? _e : {};
391
506
  const payload = {
392
507
  event_id: eventId,
393
508
  user_id: userId,
394
509
  event: eventName,
395
- timestamp: (_e = accumulated.timestamp) != null ? _e : (/* @__PURE__ */ new Date()).toISOString(),
510
+ timestamp: (_f = accumulated.timestamp) != null ? _f : (/* @__PURE__ */ new Date()).toISOString(),
396
511
  ai_data: {
397
512
  input: accumulated.input,
398
513
  output: accumulated.output,
399
514
  model: accumulated.model,
400
- convo_id: (_f = accumulated.convoId) != null ? _f : sticky.convoId
515
+ convo_id: (_g = accumulated.convoId) != null ? _g : sticky.convoId
401
516
  },
402
517
  properties: {
403
- ...(_g = accumulated.properties) != null ? _g : {},
404
- $context: {
405
- library: { name: "@raindrop-ai/opencode-plugin", version: PLUGIN_VERSION },
406
- metadata: { jsRuntime: "node" }
407
- }
518
+ ...restProperties,
519
+ ...wizardSession ? { "raindrop.wizardSession": wizardSession } : {},
520
+ $context: this.context
408
521
  },
409
522
  attachments: accumulated.attachments,
410
523
  is_pending: ((_i = (_h = accumulated.isPending) != null ? _h : sticky.isPending) != null ? _i : true) !== false
411
524
  };
412
525
  const url = `${this.baseUrl}events/track_partial`;
413
526
  if (this.debug) {
414
- console.log("[raindrop-ai/opencode-plugin] sending track_partial", {
527
+ console.log(`${this.prefix} sending track_partial`, {
415
528
  eventId,
416
529
  eventName,
417
530
  userId,
418
- isPending: payload["is_pending"],
531
+ isPending: payload.is_pending,
419
532
  inputPreview: typeof accumulated.input === "string" ? accumulated.input.slice(0, 120) : void 0,
420
533
  outputPreview: typeof accumulated.output === "string" ? accumulated.output.slice(0, 120) : void 0,
534
+ attachments: (_k = (_j = accumulated.attachments) == null ? void 0 : _j.length) != null ? _k : 0,
535
+ attachmentKinds: (_m = (_l = accumulated.attachments) == null ? void 0 : _l.map((a) => ({
536
+ type: a.type,
537
+ role: a.role,
538
+ name: a.name,
539
+ valuePreview: a.value.slice(0, 60)
540
+ }))) != null ? _m : [],
421
541
  endpoint: url
422
542
  });
423
543
  }
424
- const p = postJson(
425
- url,
426
- payload,
427
- { Authorization: `Bearer ${this.writeKey}` },
428
- { maxAttempts: 3, debug: this.debug }
429
- );
544
+ const p = postJson(url, payload, this.authHeaders(), {
545
+ maxAttempts: 3,
546
+ debug: this.debug,
547
+ sdkName: this.sdkName
548
+ });
430
549
  this.inFlight.add(p);
431
550
  try {
432
551
  try {
433
552
  await p;
434
553
  if (this.debug) {
435
- console.log(`[raindrop-ai/opencode-plugin] sent track_partial ${eventId} (${eventName})`);
554
+ console.log(`${this.prefix} sent track_partial ${eventId} (${eventName})`);
436
555
  }
437
556
  } catch (err) {
438
- if (this.debug) {
439
- const msg = err instanceof Error ? err.message : String(err);
440
- console.warn(
441
- `[raindrop-ai/opencode-plugin] failed to send track_partial (dropping): ${msg}`
442
- );
443
- }
557
+ const msg = err instanceof Error ? err.message : String(err);
558
+ console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`);
444
559
  }
445
560
  } finally {
446
561
  this.inFlight.delete(p);
447
562
  }
448
- if (!payload["is_pending"]) this.sticky.delete(eventId);
563
+ const isPending = payload.is_pending;
564
+ if (!isPending) {
565
+ this.sticky.delete(eventId);
566
+ }
449
567
  }
450
568
  };
451
569
  var TraceShipper = class {
452
570
  constructor(opts) {
453
571
  this.queue = [];
454
572
  this.inFlight = /* @__PURE__ */ new Set();
455
- var _a, _b;
456
- this.writeKey = opts.writeKey;
457
- this.baseUrl = (_a = formatEndpoint(opts.endpoint)) != null ? _a : "https://api.raindrop.ai/v1/";
573
+ var _a, _b, _c, _d, _e, _f, _g, _h;
574
+ this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
575
+ this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
576
+ this.enabled = opts.enabled !== false;
458
577
  this.debug = opts.debug;
459
- this.flushIntervalMs = (_b = opts.flushIntervalMs) != null ? _b : 1e3;
460
- this.maxBatchSize = 50;
461
- this.maxQueueSize = 5e3;
578
+ this.debugSpans = opts.debugSpans === true;
579
+ this.flushIntervalMs = (_c = opts.flushIntervalMs) != null ? _c : 1e3;
580
+ this.maxBatchSize = (_d = opts.maxBatchSize) != null ? _d : 50;
581
+ this.maxQueueSize = (_e = opts.maxQueueSize) != null ? _e : 5e3;
582
+ this.sdkName = (_f = opts.sdkName) != null ? _f : "core";
583
+ this.prefix = `[raindrop-ai/${this.sdkName}]`;
584
+ this.serviceName = (_g = opts.serviceName) != null ? _g : "raindrop.core";
585
+ this.serviceVersion = (_h = opts.serviceVersion) != null ? _h : "0.0.0";
586
+ }
587
+ isDebugEnabled() {
588
+ return this.debug;
589
+ }
590
+ authHeaders() {
591
+ return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
462
592
  }
463
593
  startSpan(args) {
464
- var _a;
594
+ var _a, _b;
465
595
  const ids = createSpanIds(args.parent);
466
- const started = nowUnixNanoString();
596
+ const started = (_a = args.startTimeUnixNano) != null ? _a : nowUnixNanoString();
467
597
  const attrs = [
468
- attrString("ai.telemetry.metadata.raindrop.eventId", args.eventId)
598
+ attrString("ai.telemetry.metadata.raindrop.eventId", args.eventId),
599
+ attrString("ai.operationId", args.operationId)
469
600
  ];
470
- if ((_a = args.attributes) == null ? void 0 : _a.length) attrs.push(...args.attributes);
601
+ if ((_b = args.attributes) == null ? void 0 : _b.length) attrs.push(...args.attributes);
471
602
  return { ids, name: args.name, startTimeUnixNano: started, attributes: attrs };
472
603
  }
473
604
  endSpan(span, extra) {
474
- var _a;
605
+ var _a, _b;
475
606
  if (span.endTimeUnixNano) return;
476
- span.endTimeUnixNano = nowUnixNanoString();
477
- if ((_a = extra == null ? void 0 : extra.attributes) == null ? void 0 : _a.length) span.attributes.push(...extra.attributes);
478
- let status;
479
- if ((extra == null ? void 0 : extra.error) !== void 0) {
607
+ span.endTimeUnixNano = (_a = extra == null ? void 0 : extra.endTimeUnixNano) != null ? _a : nowUnixNanoString();
608
+ if ((_b = extra == null ? void 0 : extra.attributes) == null ? void 0 : _b.length) {
609
+ span.attributes.push(...extra.attributes);
610
+ }
611
+ let status = extra == null ? void 0 : extra.status;
612
+ if (!status && (extra == null ? void 0 : extra.error) !== void 0) {
480
613
  const message = extra.error instanceof Error ? extra.error.message : String(extra.error);
481
614
  status = { code: SpanStatusCode.ERROR, message };
482
615
  }
@@ -490,10 +623,6 @@ var TraceShipper = class {
490
623
  });
491
624
  this.enqueue(otlp);
492
625
  }
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
626
  createSpan(args) {
498
627
  var _a;
499
628
  const ids = createSpanIds(args.parent);
@@ -512,7 +641,18 @@ var TraceShipper = class {
512
641
  this.enqueue(otlp);
513
642
  }
514
643
  enqueue(span) {
515
- if (this.queue.length >= this.maxQueueSize) this.queue.shift();
644
+ if (!this.enabled) return;
645
+ if (this.debugSpans) {
646
+ const short = (s) => s ? s.slice(-8) : "none";
647
+ console.log(
648
+ `${this.prefix}[span] name=${span.name} trace=${short(span.traceId)} span=${short(span.spanId)} parent=${short(
649
+ span.parentSpanId
650
+ )}`
651
+ );
652
+ }
653
+ if (this.queue.length >= this.maxQueueSize) {
654
+ this.queue.shift();
655
+ }
516
656
  this.queue.push(span);
517
657
  if (this.queue.length >= this.maxBatchSize) {
518
658
  void this.flush().catch(() => {
@@ -528,42 +668,34 @@ var TraceShipper = class {
528
668
  }
529
669
  }
530
670
  async flush() {
671
+ if (!this.enabled) return;
531
672
  if (this.timer) {
532
673
  clearTimeout(this.timer);
533
674
  this.timer = void 0;
534
675
  }
535
676
  while (this.queue.length > 0) {
536
677
  const batch = this.queue.splice(0, this.maxBatchSize);
537
- const body = buildExportTraceServiceRequest(batch);
678
+ const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
538
679
  const url = `${this.baseUrl}traces`;
539
- const spanNames = batch.map((span) => span.name);
540
680
  if (this.debug) {
541
- console.log("[raindrop-ai/opencode-plugin] sending traces batch", {
681
+ console.log(`${this.prefix} sending traces batch`, {
542
682
  spans: batch.length,
543
- endpoint: url,
544
- spanNames
683
+ endpoint: url
545
684
  });
546
685
  }
547
- const p = postJson(
548
- url,
549
- body,
550
- { Authorization: `Bearer ${this.writeKey}` },
551
- { maxAttempts: 3, debug: this.debug }
552
- );
686
+ const p = postJson(url, body, this.authHeaders(), {
687
+ maxAttempts: 3,
688
+ debug: this.debug,
689
+ sdkName: this.sdkName
690
+ });
553
691
  this.inFlight.add(p);
554
692
  try {
555
693
  try {
556
694
  await p;
557
- if (this.debug) {
558
- console.log(`[raindrop-ai/opencode-plugin] sent ${batch.length} spans`, {
559
- spanNames
560
- });
561
- }
695
+ if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
562
696
  } catch (err) {
563
- if (this.debug) {
564
- const msg = err instanceof Error ? err.message : String(err);
565
- console.warn(`[raindrop-ai/opencode-plugin] failed to send spans (dropping): ${msg}`);
566
- }
697
+ const msg = err instanceof Error ? err.message : String(err);
698
+ console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`);
567
699
  }
568
700
  } finally {
569
701
  this.inFlight.delete(p);
@@ -581,6 +713,41 @@ var TraceShipper = class {
581
713
  }
582
714
  };
583
715
 
716
+ // src/shipper.ts
717
+ var EventShipper2 = class extends EventShipper {
718
+ constructor(opts) {
719
+ var _a, _b, _c, _d;
720
+ super({
721
+ ...opts,
722
+ sdkName: (_a = opts.sdkName) != null ? _a : "opencode-plugin",
723
+ libraryName: (_b = opts.libraryName) != null ? _b : PLUGIN_NAME,
724
+ libraryVersion: (_c = opts.libraryVersion) != null ? _c : PLUGIN_VERSION,
725
+ defaultEventName: (_d = opts.defaultEventName) != null ? _d : "opencode_session"
726
+ });
727
+ }
728
+ };
729
+ var TraceShipper2 = class extends TraceShipper {
730
+ constructor(opts) {
731
+ var _a, _b, _c;
732
+ super({
733
+ ...opts,
734
+ sdkName: (_a = opts.sdkName) != null ? _a : "opencode-plugin",
735
+ serviceName: (_b = opts.serviceName) != null ? _b : "raindrop.opencode-plugin",
736
+ serviceVersion: (_c = opts.serviceVersion) != null ? _c : PLUGIN_VERSION
737
+ });
738
+ }
739
+ enqueue(span) {
740
+ var _a;
741
+ const attrs = (_a = span.attributes) != null ? _a : [];
742
+ attrs.unshift(
743
+ { key: "span.id", value: { stringValue: span.spanId } },
744
+ ...span.parentSpanId ? [{ key: "span.parent.id", value: { stringValue: span.parentSpanId } }] : []
745
+ );
746
+ span.attributes = attrs;
747
+ super.enqueue(span);
748
+ }
749
+ };
750
+
584
751
  // src/tracing-linkage-helpers.ts
585
752
  function createSessionParentMapHelpers({
586
753
  sessions: sessions2,
@@ -666,8 +833,6 @@ var pendingChildSessionsByCallKey = /* @__PURE__ */ new Map();
666
833
  function createSessionState(sessionId) {
667
834
  return {
668
835
  sessionId,
669
- turnNumber: 0,
670
- toolCount: 0,
671
836
  currentInput: "",
672
837
  outputParts: /* @__PURE__ */ new Map(),
673
838
  reasoningParts: /* @__PURE__ */ new Map(),
@@ -721,7 +886,7 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
721
886
  // event — session lifecycle + streaming parts
722
887
  // ------------------------------------------------------------------
723
888
  event: async ({ event }) => {
724
- var _a, _b, _c, _d, _e;
889
+ var _a, _b, _c, _d, _e, _f, _g;
725
890
  try {
726
891
  const props = event.properties;
727
892
  const info = props["info"];
@@ -939,12 +1104,11 @@ type: ${name != null ? name : "UnknownError"}`;
939
1104
  }
940
1105
  if (!state.parentId) {
941
1106
  await eventShipper.finish(state.currentEventId, {
942
- userId: state.sessionId,
1107
+ userId: (_d = (_c = state.eventMetadata) == null ? void 0 : _c.userId) != null ? _d : state.sessionId,
943
1108
  model: modelName,
944
1109
  output: outputText,
945
1110
  properties: {
946
- turn_count: state.turnNumber,
947
- tool_count: state.toolCount
1111
+ plugin_version: PLUGIN_VERSION
948
1112
  }
949
1113
  });
950
1114
  }
@@ -982,7 +1146,7 @@ type: ${name != null ? name : "UnknownError"}`;
982
1146
  await Promise.all([eventShipper.flush(), traceShipper.flush()]);
983
1147
  sessions.delete(String(sessionID));
984
1148
  } else if (event.type === "session.error") {
985
- const errorSessionID = (_c = props["sessionID"]) != null ? _c : sessionID;
1149
+ const errorSessionID = (_e = props["sessionID"]) != null ? _e : sessionID;
986
1150
  if (errorSessionID === void 0 || errorSessionID === "")
987
1151
  throw new Error("session.error: missing sessionID");
988
1152
  const state = sessions.get(String(errorSessionID));
@@ -990,7 +1154,7 @@ type: ${name != null ? name : "UnknownError"}`;
990
1154
  const errorObj = props["error"];
991
1155
  if (errorObj == null) throw new Error("session.error: missing error object");
992
1156
  const errorName = errorObj.name;
993
- const errorMessage = (_e = (_d = errorObj.data) == null ? void 0 : _d.message) != null ? _e : errorObj.name;
1157
+ const errorMessage = (_g = (_f = errorObj.data) == null ? void 0 : _f.message) != null ? _g : errorObj.name;
994
1158
  if (errorName == null && errorMessage == null)
995
1159
  throw new Error("session.error: error object must have name or data.message");
996
1160
  const errorStr = `${errorMessage != null ? errorMessage : errorName}
@@ -1017,6 +1181,7 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
1017
1181
  // chat.message — user sent a message; start a new turn with a fresh event ID
1018
1182
  // ------------------------------------------------------------------
1019
1183
  "chat.message": async (messageInput, output) => {
1184
+ var _a, _b, _c, _d, _e, _f, _g;
1020
1185
  try {
1021
1186
  const { sessionID } = messageInput;
1022
1187
  let state = sessions.get(sessionID);
@@ -1029,15 +1194,24 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
1029
1194
  traceShipper.endSpan(state.currentRootSpan);
1030
1195
  state.currentRootSpan = void 0;
1031
1196
  }
1032
- state.turnNumber++;
1033
1197
  if (output == null) throw new Error("chat.message: output is required");
1034
1198
  const parts = output.parts;
1035
1199
  if (parts == null) throw new Error("chat.message: output.parts is required");
1036
1200
  const textParts = [];
1037
1201
  const newAttachments = [];
1202
+ const baseMeta = { ...config.eventMetadata };
1203
+ const promptMeta = {};
1038
1204
  for (const part of parts) {
1039
1205
  if (part["type"] === "text" && typeof part["text"] === "string") {
1040
1206
  textParts.push(part["text"]);
1207
+ const meta = part["metadata"];
1208
+ if (meta != null && typeof meta === "object") {
1209
+ const m = meta;
1210
+ if (typeof m["userId"] === "string") promptMeta.userId = m["userId"];
1211
+ if (typeof m["eventName"] === "string") promptMeta.eventName = m["eventName"];
1212
+ if (m["properties"] != null && typeof m["properties"] === "object")
1213
+ promptMeta.properties = m["properties"];
1214
+ }
1041
1215
  } else if (part["type"] === "file") {
1042
1216
  const mimeType = part["mediaType"];
1043
1217
  const filename = part["filename"];
@@ -1049,6 +1223,11 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
1049
1223
  }
1050
1224
  }
1051
1225
  }
1226
+ state.eventMetadata = {
1227
+ ...baseMeta,
1228
+ ...promptMeta,
1229
+ properties: baseMeta.properties || promptMeta.properties ? { ...(_a = baseMeta.properties) != null ? _a : {}, ...(_b = promptMeta.properties) != null ? _b : {} } : void 0
1230
+ };
1052
1231
  const userText = textParts.join("\n");
1053
1232
  state.currentInput = userText;
1054
1233
  if (state.parentId) {
@@ -1084,9 +1263,9 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
1084
1263
  });
1085
1264
  await eventShipper.patch(state.currentEventId, {
1086
1265
  isPending: true,
1087
- userId: state.sessionId,
1266
+ userId: (_d = (_c = state.eventMetadata) == null ? void 0 : _c.userId) != null ? _d : state.sessionId,
1088
1267
  convoId: state.sessionId,
1089
- eventName: config.eventName,
1268
+ eventName: (_f = (_e = state.eventMetadata) == null ? void 0 : _e.eventName) != null ? _f : config.eventName,
1090
1269
  input: userText,
1091
1270
  ...newAttachments.length > 0 ? { attachments: newAttachments } : {},
1092
1271
  properties: {
@@ -1094,8 +1273,8 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
1094
1273
  directory,
1095
1274
  hostname,
1096
1275
  os,
1097
- turn_count: state.turnNumber,
1098
- tool_count: state.toolCount
1276
+ plugin_version: PLUGIN_VERSION,
1277
+ ...(_g = state.eventMetadata) == null ? void 0 : _g.properties
1099
1278
  }
1100
1279
  });
1101
1280
  } catch (err) {
@@ -1106,7 +1285,7 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
1106
1285
  // For child sessions, tool spans are parented directly to the parent task span.
1107
1286
  // ------------------------------------------------------------------
1108
1287
  "tool.execute.before": async (toolInput, _output) => {
1109
- var _a;
1288
+ var _a, _b, _c;
1110
1289
  try {
1111
1290
  const { tool, sessionID, callID } = toolInput;
1112
1291
  const state = sessions.get(sessionID);
@@ -1130,7 +1309,7 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
1130
1309
  ids: liveTaskSpan.ids,
1131
1310
  eventContext: {
1132
1311
  eventId: state.currentEventId,
1133
- userId: state.sessionId,
1312
+ userId: (_c = (_b = state.eventMetadata) == null ? void 0 : _b.userId) != null ? _c : state.sessionId,
1134
1313
  convoId: state.sessionId
1135
1314
  }
1136
1315
  };
@@ -1171,7 +1350,6 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
1171
1350
  const { tool, sessionID, callID, args } = toolInput;
1172
1351
  const state = sessions.get(sessionID);
1173
1352
  if (!state || !state.currentEventId) return;
1174
- state.toolCount++;
1175
1353
  if (args === void 0 || args === null)
1176
1354
  throw new Error("tool.execute.after: args is required");
1177
1355
  const toolCallArgs = typeof args === "string" ? args : JSON.stringify(args);
@@ -1225,12 +1403,6 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
1225
1403
  if (running.size === 0) runningTaskCallsBySession.delete(sessionID);
1226
1404
  }
1227
1405
  }
1228
- if (!state.parentId) {
1229
- await eventShipper.patch(state.currentEventId, {
1230
- userId: state.sessionId,
1231
- properties: { tool_count: state.toolCount }
1232
- });
1233
- }
1234
1406
  } catch (err) {
1235
1407
  console.log(
1236
1408
  `[raindrop-ai/opencode-plugin] [error] Error in tool.execute.after hook: ${err instanceof Error ? err.message : String(err)}`
@@ -1247,6 +1419,7 @@ async function plugin(input) {
1247
1419
  function appLog(level, message) {
1248
1420
  console.log(`[raindrop-ai/opencode-plugin] [${level}] ${message}`);
1249
1421
  }
1422
+ appLog("info", `Loading ${PLUGIN_NAME} v${PLUGIN_VERSION}`);
1250
1423
  if (!config.writeKey) {
1251
1424
  appLog(
1252
1425
  "warn",
@@ -1257,12 +1430,12 @@ async function plugin(input) {
1257
1430
  if (config.debug) {
1258
1431
  appLog("info", `Raindrop tracing enabled (endpoint: ${config.endpoint})`);
1259
1432
  }
1260
- const eventShipper = new EventShipper({
1433
+ const eventShipper = new EventShipper2({
1261
1434
  writeKey: config.writeKey,
1262
1435
  endpoint: config.endpoint,
1263
1436
  debug: config.debug
1264
1437
  });
1265
- const traceShipper = new TraceShipper({
1438
+ const traceShipper = new TraceShipper2({
1266
1439
  writeKey: config.writeKey,
1267
1440
  endpoint: config.endpoint,
1268
1441
  debug: config.debug