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