@raindrop-ai/opencode-plugin 0.0.4 → 0.0.5

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