@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.cjs CHANGED
@@ -53,86 +53,30 @@ function loadConfig(projectDirectory) {
53
53
  };
54
54
  }
55
55
 
56
- // package.json
57
- var package_default = {
58
- name: "@raindrop-ai/opencode-plugin",
59
- version: "0.0.4",
60
- description: "Raindrop observability plugin for OpenCode \u2014 automatic session/event/span tracing",
61
- type: "module",
62
- main: "dist/index.cjs",
63
- module: "dist/index.js",
64
- types: "dist/index.d.ts",
65
- exports: {
66
- ".": {
67
- types: "./dist/index.d.ts",
68
- import: "./dist/index.js",
69
- require: "./dist/index.cjs"
70
- }
71
- },
72
- sideEffects: false,
73
- files: [
74
- "dist/**"
75
- ],
76
- scripts: {
77
- build: "tsup",
78
- dev: "tsup --watch",
79
- clean: "rm -rf dist",
80
- test: "cd tests && yarn test"
81
- },
82
- peerDependencies: {
83
- "@opencode-ai/plugin": ">=1.2.0",
84
- "@opencode-ai/sdk": ">=1.2.0"
85
- },
86
- peerDependenciesMeta: {
87
- "@opencode-ai/sdk": {
88
- optional: true
89
- }
90
- },
91
- devDependencies: {
92
- "@opencode-ai/plugin": "^1.2.0",
93
- "@opencode-ai/sdk": "^1.2.0",
94
- "@types/node": "^20.11.17",
95
- tsup: "^8.4.0",
96
- typescript: "^5.3.3"
97
- },
98
- tsup: {
99
- entry: [
100
- "src/index.ts"
101
- ],
102
- format: [
103
- "cjs",
104
- "esm"
105
- ],
106
- dts: {
107
- resolve: true
108
- },
109
- clean: true
110
- },
111
- publishConfig: {
112
- access: "public"
113
- }
114
- };
115
-
116
- // src/shipper.ts
117
- var PLUGIN_VERSION = package_default.version;
118
- function getCryptoObj() {
119
- return globalThis.crypto;
56
+ // ../core/dist/chunk-FTBZHS25.js
57
+ function getCrypto() {
58
+ const c = globalThis.crypto;
59
+ return c;
120
60
  }
121
61
  function randomBytes(length) {
122
- const c = getCryptoObj();
62
+ const cryptoObj = getCrypto();
123
63
  const out = new Uint8Array(length);
124
- if (c == null ? void 0 : c.getRandomValues) {
125
- c.getRandomValues(out);
64
+ if (cryptoObj && typeof cryptoObj.getRandomValues === "function") {
65
+ cryptoObj.getRandomValues(out);
126
66
  return out;
127
67
  }
128
68
  for (let i = 0; i < out.length; i++) out[i] = Math.floor(Math.random() * 256);
129
69
  return out;
130
70
  }
131
71
  function base64Encode(bytes) {
132
- const buf = globalThis.Buffer;
133
- if (buf) return buf.from(bytes).toString("base64");
72
+ const maybeBuffer = globalThis.Buffer;
73
+ if (maybeBuffer) {
74
+ return maybeBuffer.from(bytes).toString("base64");
75
+ }
134
76
  let binary = "";
135
- 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
+ }
136
80
  const btoaFn = globalThis.btoa;
137
81
  if (typeof btoaFn === "function") return btoaFn(binary);
138
82
  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
@@ -153,69 +97,9 @@ function base64Encode(bytes) {
153
97
  }
154
98
  return out;
155
99
  }
156
- var SERVICE_NAME = "raindrop.opencode-plugin";
157
- var SpanStatusCode = {
158
- ERROR: 2
159
- };
160
100
  function generateId() {
161
101
  return base64Encode(randomBytes(12)).replace(/[+/=]/g, "").slice(0, 16);
162
102
  }
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
103
  function wait(ms) {
220
104
  return new Promise((resolve) => setTimeout(resolve, ms));
221
105
  }
@@ -228,7 +112,7 @@ function parseRetryAfter(headers) {
228
112
  const value = (_a = headers.get("Retry-After")) != null ? _a : headers.get("retry-after");
229
113
  if (!value) return void 0;
230
114
  const asNumber = Number(value);
231
- if (!Number.isNaN(asNumber)) return asNumber * 1e3;
115
+ if (value.trim() !== "" && !Number.isNaN(asNumber)) return asNumber * 1e3;
232
116
  const asDate = new Date(value).getTime();
233
117
  if (!Number.isNaN(asDate)) {
234
118
  const delta = asDate - Date.now();
@@ -237,26 +121,29 @@ function parseRetryAfter(headers) {
237
121
  return void 0;
238
122
  }
239
123
  function getRetryDelayMs(attemptNumber, previousError) {
240
- if (previousError !== null && typeof previousError === "object" && "retryAfterMs" in previousError) {
124
+ if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
241
125
  const v = previousError.retryAfterMs;
242
126
  if (typeof v === "number") return Math.max(0, v);
243
127
  }
244
128
  if (attemptNumber <= 1) return 0;
245
- return 500 * Math.pow(2, attemptNumber - 2);
129
+ const base = 500;
130
+ const factor = Math.pow(2, attemptNumber - 2);
131
+ return base * factor;
246
132
  }
247
133
  async function withRetry(operation, opName, opts) {
134
+ const prefix = opts.sdkName ? `[raindrop-ai/${opts.sdkName}]` : "[raindrop-ai/core]";
248
135
  let lastError = void 0;
249
- for (let attempt = 1; attempt <= opts.maxAttempts; attempt++) {
250
- if (attempt > 1) {
251
- const delay = getRetryDelayMs(attempt, lastError);
136
+ for (let attemptNumber = 1; attemptNumber <= opts.maxAttempts; attemptNumber++) {
137
+ if (attemptNumber > 1) {
138
+ const delay = getRetryDelayMs(attemptNumber, lastError);
252
139
  if (opts.debug) {
253
140
  console.warn(
254
- `[raindrop-ai/opencode-plugin] ${opName} retry ${attempt}/${opts.maxAttempts} in ${delay}ms`
141
+ `${prefix} ${opName} retry ${attemptNumber}/${opts.maxAttempts} in ${delay}ms`
255
142
  );
256
143
  }
257
144
  if (delay > 0) await wait(delay);
258
145
  } else if (opts.debug) {
259
- console.log(`[raindrop-ai/opencode-plugin] ${opName} attempt ${attempt}/${opts.maxAttempts}`);
146
+ console.log(`${prefix} ${opName} attempt ${attemptNumber}/${opts.maxAttempts}`);
260
147
  }
261
148
  try {
262
149
  return await operation();
@@ -265,20 +152,26 @@ async function withRetry(operation, opName, opts) {
265
152
  if (opts.debug) {
266
153
  const msg = err instanceof Error ? err.message : String(err);
267
154
  console.warn(
268
- `[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)" : ""}`
269
156
  );
270
157
  }
271
- if (attempt === opts.maxAttempts) break;
158
+ if (lastError && typeof lastError === "object" && "retryable" in lastError && !lastError.retryable)
159
+ break;
160
+ if (attemptNumber === opts.maxAttempts) break;
272
161
  }
273
162
  }
274
163
  throw lastError instanceof Error ? lastError : new Error(String(lastError));
275
164
  }
276
165
  async function postJson(url, body, headers, opts) {
166
+ const opName = `POST ${url}`;
277
167
  await withRetry(
278
168
  async () => {
279
169
  const resp = await fetch(url, {
280
170
  method: "POST",
281
- headers: { "Content-Type": "application/json", ...headers },
171
+ headers: {
172
+ "Content-Type": "application/json",
173
+ ...headers
174
+ },
282
175
  body: JSON.stringify(body)
283
176
  });
284
177
  if (!resp.ok) {
@@ -288,13 +181,71 @@ async function postJson(url, body, headers, opts) {
288
181
  );
289
182
  const retryAfterMs = parseRetryAfter(resp.headers);
290
183
  if (typeof retryAfterMs === "number") err.retryAfterMs = retryAfterMs;
184
+ err.retryable = resp.status === 429 || resp.status >= 500;
291
185
  throw err;
292
186
  }
293
187
  },
294
- `POST ${url}`,
188
+ opName,
295
189
  opts
296
190
  );
297
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
+ }
298
249
  function mergePatches(target, source) {
299
250
  var _a, _b, _c, _d;
300
251
  const out = { ...target, ...source };
@@ -312,34 +263,58 @@ var EventShipper = class {
312
263
  this.sticky = /* @__PURE__ */ new Map();
313
264
  this.timers = /* @__PURE__ */ new Map();
314
265
  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/";
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;
318
270
  this.debug = opts.debug;
319
- 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}` } : {};
320
292
  }
321
293
  async patch(eventId, patch) {
322
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
323
- 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;
324
297
  if (this.debug) {
325
- console.log("[raindrop-ai/opencode-plugin] queue patch", {
298
+ console.log(`${this.prefix} queue patch`, {
326
299
  eventId,
327
300
  userId: patch.userId,
301
+ eventName: patch.eventName,
328
302
  hasInput: typeof patch.input === "string" && patch.input.length > 0,
329
303
  hasOutput: typeof patch.output === "string" && patch.output.length > 0,
304
+ attachments: (_b = (_a = patch.attachments) == null ? void 0 : _a.length) != null ? _b : 0,
330
305
  isPending: patch.isPending
331
306
  });
332
307
  }
333
- const sticky = (_a = this.sticky.get(eventId)) != null ? _a : {};
334
- 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 : {};
335
310
  const merged = mergePatches(existing, patch);
336
- 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;
337
312
  this.buffers.set(eventId, merged);
338
313
  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
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
343
318
  });
344
319
  const t = this.timers.get(eventId);
345
320
  if (t) clearTimeout(t);
@@ -357,6 +332,7 @@ var EventShipper = class {
357
332
  await this.patch(eventId, { ...patch, isPending: false });
358
333
  }
359
334
  async flush() {
335
+ if (!this.enabled) return;
360
336
  const ids = [...this.buffers.keys()];
361
337
  await Promise.all(ids.map((id) => this.flushOne(id)));
362
338
  await Promise.all([...this.inFlight].map((p) => p.catch(() => {
@@ -367,8 +343,70 @@ var EventShipper = class {
367
343
  this.timers.clear();
368
344
  await this.flush();
369
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
+ }
370
407
  async flushOne(eventId) {
371
- 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;
372
410
  const timer = this.timers.get(eventId);
373
411
  if (timer) {
374
412
  clearTimeout(timer);
@@ -378,105 +416,123 @@ var EventShipper = class {
378
416
  this.buffers.delete(eventId);
379
417
  if (!accumulated) return;
380
418
  const sticky = (_a = this.sticky.get(eventId)) != null ? _a : {};
381
- 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;
382
420
  const userId = (_d = accumulated.userId) != null ? _d : sticky.userId;
383
421
  if (!userId) {
384
422
  if (this.debug) {
385
- console.warn(
386
- `[raindrop-ai/opencode-plugin] skipping track_partial for ${eventId}: missing userId`
387
- );
423
+ console.warn(`${this.prefix} skipping track_partial for ${eventId}: missing userId`);
388
424
  }
425
+ this.sticky.delete(eventId);
389
426
  return;
390
427
  }
428
+ const { wizardSession, ...restProperties } = (_e = accumulated.properties) != null ? _e : {};
391
429
  const payload = {
392
430
  event_id: eventId,
393
431
  user_id: userId,
394
432
  event: eventName,
395
- timestamp: (_e = accumulated.timestamp) != null ? _e : (/* @__PURE__ */ new Date()).toISOString(),
433
+ timestamp: (_f = accumulated.timestamp) != null ? _f : (/* @__PURE__ */ new Date()).toISOString(),
396
434
  ai_data: {
397
435
  input: accumulated.input,
398
436
  output: accumulated.output,
399
437
  model: accumulated.model,
400
- convo_id: (_f = accumulated.convoId) != null ? _f : sticky.convoId
438
+ convo_id: (_g = accumulated.convoId) != null ? _g : sticky.convoId
401
439
  },
402
440
  properties: {
403
- ...(_g = accumulated.properties) != null ? _g : {},
404
- $context: {
405
- library: { name: "@raindrop-ai/opencode-plugin", version: PLUGIN_VERSION },
406
- metadata: { jsRuntime: "node" }
407
- }
441
+ ...restProperties,
442
+ ...wizardSession ? { "raindrop.wizardSession": wizardSession } : {},
443
+ $context: this.context
408
444
  },
409
445
  attachments: accumulated.attachments,
410
446
  is_pending: ((_i = (_h = accumulated.isPending) != null ? _h : sticky.isPending) != null ? _i : true) !== false
411
447
  };
412
448
  const url = `${this.baseUrl}events/track_partial`;
413
449
  if (this.debug) {
414
- console.log("[raindrop-ai/opencode-plugin] sending track_partial", {
450
+ console.log(`${this.prefix} sending track_partial`, {
415
451
  eventId,
416
452
  eventName,
417
453
  userId,
418
- isPending: payload["is_pending"],
454
+ isPending: payload.is_pending,
419
455
  inputPreview: typeof accumulated.input === "string" ? accumulated.input.slice(0, 120) : void 0,
420
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 : [],
421
464
  endpoint: url
422
465
  });
423
466
  }
424
- const p = postJson(
425
- url,
426
- payload,
427
- { Authorization: `Bearer ${this.writeKey}` },
428
- { maxAttempts: 3, debug: this.debug }
429
- );
467
+ const p = postJson(url, payload, this.authHeaders(), {
468
+ maxAttempts: 3,
469
+ debug: this.debug,
470
+ sdkName: this.sdkName
471
+ });
430
472
  this.inFlight.add(p);
431
473
  try {
432
474
  try {
433
475
  await p;
434
476
  if (this.debug) {
435
- console.log(`[raindrop-ai/opencode-plugin] sent track_partial ${eventId} (${eventName})`);
477
+ console.log(`${this.prefix} sent track_partial ${eventId} (${eventName})`);
436
478
  }
437
479
  } 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
- }
480
+ const msg = err instanceof Error ? err.message : String(err);
481
+ console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`);
444
482
  }
445
483
  } finally {
446
484
  this.inFlight.delete(p);
447
485
  }
448
- if (!payload["is_pending"]) this.sticky.delete(eventId);
486
+ const isPending = payload.is_pending;
487
+ if (!isPending) {
488
+ this.sticky.delete(eventId);
489
+ }
449
490
  }
450
491
  };
451
492
  var TraceShipper = class {
452
493
  constructor(opts) {
453
494
  this.queue = [];
454
495
  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/";
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;
458
500
  this.debug = opts.debug;
459
- this.flushIntervalMs = (_b = opts.flushIntervalMs) != null ? _b : 1e3;
460
- this.maxBatchSize = 50;
461
- 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}` } : {};
462
515
  }
463
516
  startSpan(args) {
464
- var _a;
517
+ var _a, _b;
465
518
  const ids = createSpanIds(args.parent);
466
- const started = nowUnixNanoString();
519
+ const started = (_a = args.startTimeUnixNano) != null ? _a : nowUnixNanoString();
467
520
  const attrs = [
468
- attrString("ai.telemetry.metadata.raindrop.eventId", args.eventId)
521
+ attrString("ai.telemetry.metadata.raindrop.eventId", args.eventId),
522
+ attrString("ai.operationId", args.operationId)
469
523
  ];
470
- 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);
471
525
  return { ids, name: args.name, startTimeUnixNano: started, attributes: attrs };
472
526
  }
473
527
  endSpan(span, extra) {
474
- var _a;
528
+ var _a, _b;
475
529
  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) {
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) {
480
536
  const message = extra.error instanceof Error ? extra.error.message : String(extra.error);
481
537
  status = { code: SpanStatusCode.ERROR, message };
482
538
  }
@@ -490,10 +546,6 @@ var TraceShipper = class {
490
546
  });
491
547
  this.enqueue(otlp);
492
548
  }
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
549
  createSpan(args) {
498
550
  var _a;
499
551
  const ids = createSpanIds(args.parent);
@@ -512,7 +564,18 @@ var TraceShipper = class {
512
564
  this.enqueue(otlp);
513
565
  }
514
566
  enqueue(span) {
515
- 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
+ }
516
579
  this.queue.push(span);
517
580
  if (this.queue.length >= this.maxBatchSize) {
518
581
  void this.flush().catch(() => {
@@ -528,42 +591,34 @@ var TraceShipper = class {
528
591
  }
529
592
  }
530
593
  async flush() {
594
+ if (!this.enabled) return;
531
595
  if (this.timer) {
532
596
  clearTimeout(this.timer);
533
597
  this.timer = void 0;
534
598
  }
535
599
  while (this.queue.length > 0) {
536
600
  const batch = this.queue.splice(0, this.maxBatchSize);
537
- const body = buildExportTraceServiceRequest(batch);
601
+ const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
538
602
  const url = `${this.baseUrl}traces`;
539
- const spanNames = batch.map((span) => span.name);
540
603
  if (this.debug) {
541
- console.log("[raindrop-ai/opencode-plugin] sending traces batch", {
604
+ console.log(`${this.prefix} sending traces batch`, {
542
605
  spans: batch.length,
543
- endpoint: url,
544
- spanNames
606
+ endpoint: url
545
607
  });
546
608
  }
547
- const p = postJson(
548
- url,
549
- body,
550
- { Authorization: `Bearer ${this.writeKey}` },
551
- { maxAttempts: 3, debug: this.debug }
552
- );
609
+ const p = postJson(url, body, this.authHeaders(), {
610
+ maxAttempts: 3,
611
+ debug: this.debug,
612
+ sdkName: this.sdkName
613
+ });
553
614
  this.inFlight.add(p);
554
615
  try {
555
616
  try {
556
617
  await p;
557
- if (this.debug) {
558
- console.log(`[raindrop-ai/opencode-plugin] sent ${batch.length} spans`, {
559
- spanNames
560
- });
561
- }
618
+ if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
562
619
  } 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
- }
620
+ const msg = err instanceof Error ? err.message : String(err);
621
+ console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`);
567
622
  }
568
623
  } finally {
569
624
  this.inFlight.delete(p);
@@ -581,6 +636,105 @@ var TraceShipper = class {
581
636
  }
582
637
  };
583
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
+
584
738
  // src/tracing-linkage-helpers.ts
585
739
  function createSessionParentMapHelpers({
586
740
  sessions: sessions2,
@@ -1257,12 +1411,12 @@ async function plugin(input) {
1257
1411
  if (config.debug) {
1258
1412
  appLog("info", `Raindrop tracing enabled (endpoint: ${config.endpoint})`);
1259
1413
  }
1260
- const eventShipper = new EventShipper({
1414
+ const eventShipper = new EventShipper2({
1261
1415
  writeKey: config.writeKey,
1262
1416
  endpoint: config.endpoint,
1263
1417
  debug: config.debug
1264
1418
  });
1265
- const traceShipper = new TraceShipper({
1419
+ const traceShipper = new TraceShipper2({
1266
1420
  writeKey: config.writeKey,
1267
1421
  endpoint: config.endpoint,
1268
1422
  debug: config.debug