@raindrop-ai/opencode-plugin 0.0.11 → 0.0.12

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 +278 -152
  2. package/dist/index.js +278 -152
  3. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -24,124 +24,7 @@ __export(index_exports, {
24
24
  });
25
25
  module.exports = __toCommonJS(index_exports);
26
26
 
27
- // src/config.ts
28
- var import_node_fs = require("fs");
29
- var import_node_os = require("os");
30
- var import_node_path = require("path");
31
- function loadConfig(projectDirectory) {
32
- var _a, _b, _c, _d, _e, _f, _g;
33
- let merged = {};
34
- const configPaths = [
35
- (0, import_node_path.join)((0, import_node_os.homedir)(), ".config", "opencode", "raindrop.json"),
36
- (0, import_node_path.join)(projectDirectory, ".opencode", "raindrop.json")
37
- ];
38
- for (const configPath of configPaths) {
39
- try {
40
- if ((0, import_node_fs.existsSync)(configPath)) {
41
- const content = (0, import_node_fs.readFileSync)(configPath, "utf-8");
42
- const parsed = JSON.parse(content);
43
- merged = { ...merged, ...parsed };
44
- }
45
- } catch (e) {
46
- }
47
- }
48
- let eventMetadata;
49
- const envMeta = process.env["RAINDROP_EVENT_METADATA"];
50
- if (envMeta) {
51
- try {
52
- eventMetadata = JSON.parse(envMeta);
53
- } catch (e) {
54
- }
55
- }
56
- return {
57
- writeKey: (_b = (_a = process.env["RAINDROP_WRITE_KEY"]) != null ? _a : merged.write_key) != null ? _b : "",
58
- endpoint: (_d = (_c = process.env["RAINDROP_API_URL"]) != null ? _c : merged.api_url) != null ? _d : "https://api.raindrop.ai/v1",
59
- eventName: (_e = merged.event_name) != null ? _e : "opencode_session",
60
- debug: process.env["RAINDROP_DEBUG"] === "true" ? true : (_f = merged.debug) != null ? _f : false,
61
- captureSystemPrompt: process.env["RAINDROP_CAPTURE_SYSTEM_PROMPT"] !== void 0 ? process.env["RAINDROP_CAPTURE_SYSTEM_PROMPT"] === "true" : (_g = merged.capture_system_prompt) != null ? _g : false,
62
- eventMetadata
63
- };
64
- }
65
-
66
- // package.json
67
- var package_default = {
68
- name: "@raindrop-ai/opencode-plugin",
69
- version: "0.0.11",
70
- description: "Raindrop observability plugin for OpenCode \u2014 automatic session/event/span tracing",
71
- type: "module",
72
- main: "dist/index.js",
73
- module: "dist/index.js",
74
- types: "dist/index.d.ts",
75
- license: "MIT",
76
- repository: {
77
- type: "git",
78
- url: "git+https://github.com/raindrop-ai/raindrop-js.git",
79
- directory: "packages/opencode-plugin"
80
- },
81
- homepage: "https://github.com/raindrop-ai/raindrop-js/tree/main/packages/opencode-plugin#readme",
82
- bugs: {
83
- url: "https://github.com/raindrop-ai/raindrop-js/issues"
84
- },
85
- exports: {
86
- ".": {
87
- types: "./dist/index.d.ts",
88
- import: "./dist/index.js",
89
- require: "./dist/index.cjs"
90
- }
91
- },
92
- sideEffects: false,
93
- files: [
94
- "dist/**"
95
- ],
96
- scripts: {
97
- build: "tsup",
98
- dev: "tsup --watch",
99
- clean: "rm -rf dist",
100
- test: "cd tests && pnpm test"
101
- },
102
- peerDependencies: {
103
- "@opencode-ai/plugin": ">=1.3.0",
104
- "@opencode-ai/sdk": ">=1.3.0"
105
- },
106
- peerDependenciesMeta: {
107
- "@opencode-ai/sdk": {
108
- optional: true
109
- }
110
- },
111
- devDependencies: {
112
- "@raindrop-ai/core": "workspace:*",
113
- "@opencode-ai/plugin": "^1.3.3",
114
- "@opencode-ai/sdk": "^1.3.3",
115
- "@types/node": "^20.11.17",
116
- tsup: "^8.4.0",
117
- typescript: "^5.3.3"
118
- },
119
- tsup: {
120
- entry: [
121
- "src/index.ts"
122
- ],
123
- format: [
124
- "cjs",
125
- "esm"
126
- ],
127
- dts: {
128
- resolve: true
129
- },
130
- clean: true,
131
- noExternal: [
132
- "@raindrop-ai/core"
133
- ]
134
- },
135
- publishConfig: {
136
- access: "public"
137
- }
138
- };
139
-
140
- // src/package-info.ts
141
- var PLUGIN_NAME = package_default.name;
142
- var PLUGIN_VERSION = package_default.version;
143
-
144
- // ../core/dist/chunk-4UCYIEH4.js
27
+ // ../core/dist/chunk-OLYXZCOK.js
145
28
  function getCrypto() {
146
29
  const c = globalThis.crypto;
147
30
  return c;
@@ -334,6 +217,91 @@ function buildExportTraceServiceRequest(spans, serviceName = "raindrop.core", se
334
217
  ]
335
218
  };
336
219
  }
220
+ var LOCAL_DEBUGGER_ENV_VAR = "RAINDROP_LOCAL_DEBUGGER";
221
+ var WORKSHOP_ENV_VAR = "RAINDROP_WORKSHOP";
222
+ var DEFAULT_LOCAL_WORKSHOP_URL = "http://localhost:5899/v1/";
223
+ function readEnvVar(name) {
224
+ var _a;
225
+ try {
226
+ const env = (_a = globalThis == null ? void 0 : globalThis.process) == null ? void 0 : _a.env;
227
+ if (env && typeof env[name] === "string" && env[name].length > 0) {
228
+ return env[name];
229
+ }
230
+ } catch (e) {
231
+ }
232
+ return void 0;
233
+ }
234
+ function readWorkshopEnv() {
235
+ const raw = readEnvVar(WORKSHOP_ENV_VAR);
236
+ if (raw === void 0) return void 0;
237
+ const trimmed = raw.trim();
238
+ if (trimmed.length === 0) return void 0;
239
+ if (/^https?:\/\//i.test(trimmed)) return { url: trimmed };
240
+ if (/^(1|true|yes|on)$/i.test(trimmed)) return "enable";
241
+ if (/^(0|false|no|off)$/i.test(trimmed)) return "disable";
242
+ return void 0;
243
+ }
244
+ function isLocalDevHost(hostname) {
245
+ if (!hostname) return false;
246
+ if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "0.0.0.0" || hostname === "::1") {
247
+ return true;
248
+ }
249
+ if (hostname.endsWith(".localhost")) return true;
250
+ return false;
251
+ }
252
+ function readRuntimeHostname() {
253
+ try {
254
+ const loc = globalThis == null ? void 0 : globalThis.location;
255
+ if (loc && typeof loc.hostname === "string" && loc.hostname.length > 0) {
256
+ return loc.hostname;
257
+ }
258
+ } catch (e) {
259
+ }
260
+ return void 0;
261
+ }
262
+ function shouldAutoEnableLocalWorkshop() {
263
+ if (isLocalDevHost(readRuntimeHostname())) return true;
264
+ if (readEnvVar("NODE_ENV") === "development") return true;
265
+ return false;
266
+ }
267
+ function resolveLocalDebuggerBaseUrl(baseUrl) {
268
+ var _a, _b, _c;
269
+ if (baseUrl === null) return null;
270
+ if (typeof baseUrl === "string" && baseUrl.length > 0) {
271
+ return (_a = formatEndpoint(baseUrl)) != null ? _a : null;
272
+ }
273
+ const explicitUrlEnv = readEnvVar(LOCAL_DEBUGGER_ENV_VAR);
274
+ if (explicitUrlEnv) return (_b = formatEndpoint(explicitUrlEnv)) != null ? _b : null;
275
+ const workshopEnv = readWorkshopEnv();
276
+ if (workshopEnv === "disable") return null;
277
+ if (workshopEnv === "enable") return DEFAULT_LOCAL_WORKSHOP_URL;
278
+ if (workshopEnv && "url" in workshopEnv) return (_c = formatEndpoint(workshopEnv.url)) != null ? _c : null;
279
+ if (shouldAutoEnableLocalWorkshop()) return DEFAULT_LOCAL_WORKSHOP_URL;
280
+ return null;
281
+ }
282
+ function mirrorTraceExportToLocalDebugger(body, options = {}) {
283
+ var _a;
284
+ const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
285
+ if (!baseUrl) return;
286
+ void postJson(`${baseUrl}traces`, body, {}, {
287
+ maxAttempts: 1,
288
+ debug: (_a = options.debug) != null ? _a : false,
289
+ sdkName: options.sdkName
290
+ }).catch(() => {
291
+ });
292
+ }
293
+ function mirrorPartialEventToLocalDebugger(event, options = {}) {
294
+ var _a;
295
+ const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
296
+ if (!baseUrl) return;
297
+ const headers = options.writeKey ? { Authorization: `Bearer ${options.writeKey}` } : {};
298
+ void postJson(`${baseUrl}events/track_partial`, event, headers, {
299
+ maxAttempts: 1,
300
+ debug: (_a = options.debug) != null ? _a : false,
301
+ sdkName: options.sdkName
302
+ }).catch(() => {
303
+ });
304
+ }
337
305
  function mergePatches(target, source) {
338
306
  var _a, _b, _c, _d;
339
307
  const out = { ...target, ...source };
@@ -351,7 +319,7 @@ var EventShipper = class {
351
319
  this.sticky = /* @__PURE__ */ new Map();
352
320
  this.timers = /* @__PURE__ */ new Map();
353
321
  this.inFlight = /* @__PURE__ */ new Set();
354
- var _a, _b, _c, _d, _e, _f, _g;
322
+ var _a, _b, _c, _d, _e, _f, _g, _h;
355
323
  this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
356
324
  this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
357
325
  this.enabled = opts.enabled !== false;
@@ -360,11 +328,15 @@ var EventShipper = class {
360
328
  this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
361
329
  this.prefix = `[raindrop-ai/${this.sdkName}]`;
362
330
  this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
331
+ this.localDebuggerUrl = (_f = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _f : void 0;
332
+ if (this.debug && this.localDebuggerUrl) {
333
+ console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
334
+ }
363
335
  const isNode = typeof process !== "undefined" && typeof process.version === "string";
364
336
  this.context = {
365
337
  library: {
366
- name: (_f = opts.libraryName) != null ? _f : "@raindrop-ai/core",
367
- version: (_g = opts.libraryVersion) != null ? _g : "0.0.0"
338
+ name: (_g = opts.libraryName) != null ? _g : "@raindrop-ai/core",
339
+ version: (_h = opts.libraryVersion) != null ? _h : "0.0.0"
368
340
  },
369
341
  metadata: {
370
342
  jsRuntime: isNode ? "node" : "web",
@@ -450,6 +422,7 @@ var EventShipper = class {
450
422
  }
451
423
  }
452
424
  ];
425
+ if (!this.writeKey) return;
453
426
  const url = `${this.baseUrl}signals/track`;
454
427
  try {
455
428
  await postJson(url, body, this.authHeaders(), {
@@ -480,6 +453,7 @@ var EventShipper = class {
480
453
  traits: (_a = user.traits) != null ? _a : {}
481
454
  };
482
455
  });
456
+ if (!this.writeKey) return;
483
457
  if (body.length === 0) return;
484
458
  const url = `${this.baseUrl}users/identify`;
485
459
  try {
@@ -556,6 +530,18 @@ var EventShipper = class {
556
530
  endpoint: url
557
531
  });
558
532
  }
533
+ if (this.localDebuggerUrl) {
534
+ mirrorPartialEventToLocalDebugger(payload, {
535
+ baseUrl: this.localDebuggerUrl,
536
+ writeKey: this.writeKey,
537
+ debug: this.debug,
538
+ sdkName: this.sdkName
539
+ });
540
+ }
541
+ if (!this.writeKey) {
542
+ if (!isPending) this.sticky.delete(eventId);
543
+ return;
544
+ }
559
545
  const p = postJson(url, payload, this.authHeaders(), {
560
546
  maxAttempts: 3,
561
547
  debug: this.debug,
@@ -580,28 +566,11 @@ var EventShipper = class {
580
566
  }
581
567
  }
582
568
  };
583
- var LOCAL_DEBUGGER_ENV_VAR = "RAINDROP_LOCAL_DEBUGGER";
584
- function resolveLocalDebuggerBaseUrl(baseUrl) {
585
- var _a, _b, _c;
586
- const resolved = (_b = baseUrl != null ? baseUrl : typeof process !== "undefined" ? (_a = process.env) == null ? void 0 : _a[LOCAL_DEBUGGER_ENV_VAR] : void 0) != null ? _b : null;
587
- return resolved ? (_c = formatEndpoint(resolved)) != null ? _c : null : null;
588
- }
589
- function mirrorTraceExportToLocalDebugger(body, options = {}) {
590
- var _a;
591
- const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
592
- if (!baseUrl) return;
593
- void postJson(`${baseUrl}traces`, body, {}, {
594
- maxAttempts: 1,
595
- debug: (_a = options.debug) != null ? _a : false,
596
- sdkName: options.sdkName
597
- }).catch(() => {
598
- });
599
- }
600
569
  var TraceShipper = class {
601
570
  constructor(opts) {
602
571
  this.queue = [];
603
572
  this.inFlight = /* @__PURE__ */ new Set();
604
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
573
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
605
574
  this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
606
575
  this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
607
576
  this.enabled = opts.enabled !== false;
@@ -614,12 +583,9 @@ var TraceShipper = class {
614
583
  this.prefix = `[raindrop-ai/${this.sdkName}]`;
615
584
  this.serviceName = (_g = opts.serviceName) != null ? _g : "raindrop.core";
616
585
  this.serviceVersion = (_h = opts.serviceVersion) != null ? _h : "0.0.0";
617
- const localDebugger = typeof process !== "undefined" ? (_i = process.env) == null ? void 0 : _i.RAINDROP_LOCAL_DEBUGGER : void 0;
618
- if (localDebugger) {
619
- this.localDebuggerUrl = (_j = resolveLocalDebuggerBaseUrl(localDebugger)) != null ? _j : void 0;
620
- if (this.debug) {
621
- console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
622
- }
586
+ this.localDebuggerUrl = (_i = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _i : void 0;
587
+ if (this.debug && this.localDebuggerUrl) {
588
+ console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
623
589
  }
624
590
  }
625
591
  isDebugEnabled() {
@@ -747,6 +713,7 @@ var TraceShipper = class {
747
713
  }
748
714
  while (this.queue.length > 0) {
749
715
  const batch = this.queue.splice(0, this.maxBatchSize);
716
+ if (!this.writeKey) continue;
750
717
  const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
751
718
  const url = `${this.baseUrl}traces`;
752
719
  if (this.debug) {
@@ -789,6 +756,134 @@ var TraceShipper = class {
789
756
  var import_async_hooks = require("async_hooks");
790
757
  globalThis.RAINDROP_ASYNC_LOCAL_STORAGE = import_async_hooks.AsyncLocalStorage;
791
758
 
759
+ // src/config.ts
760
+ var import_node_fs = require("fs");
761
+ var import_node_os = require("os");
762
+ var import_node_path = require("path");
763
+ function loadConfig(projectDirectory) {
764
+ var _a, _b, _c, _d, _e, _f, _g;
765
+ let merged = {};
766
+ const configPaths = [
767
+ (0, import_node_path.join)((0, import_node_os.homedir)(), ".config", "opencode", "raindrop.json"),
768
+ (0, import_node_path.join)(projectDirectory, ".opencode", "raindrop.json")
769
+ ];
770
+ for (const configPath of configPaths) {
771
+ try {
772
+ if ((0, import_node_fs.existsSync)(configPath)) {
773
+ const content = (0, import_node_fs.readFileSync)(configPath, "utf-8");
774
+ const parsed = JSON.parse(content);
775
+ merged = { ...merged, ...parsed };
776
+ }
777
+ } catch (e) {
778
+ }
779
+ }
780
+ let eventMetadata;
781
+ const envMeta = process.env["RAINDROP_EVENT_METADATA"];
782
+ if (envMeta) {
783
+ try {
784
+ eventMetadata = JSON.parse(envMeta);
785
+ } catch (e) {
786
+ }
787
+ }
788
+ return {
789
+ writeKey: (_b = (_a = process.env["RAINDROP_WRITE_KEY"]) != null ? _a : merged.write_key) != null ? _b : "",
790
+ endpoint: (_d = (_c = process.env["RAINDROP_API_URL"]) != null ? _c : merged.api_url) != null ? _d : "https://api.raindrop.ai/v1",
791
+ eventName: (_e = merged.event_name) != null ? _e : "opencode_session",
792
+ debug: process.env["RAINDROP_DEBUG"] === "true" ? true : (_f = merged.debug) != null ? _f : false,
793
+ captureSystemPrompt: process.env["RAINDROP_CAPTURE_SYSTEM_PROMPT"] !== void 0 ? process.env["RAINDROP_CAPTURE_SYSTEM_PROMPT"] === "true" : (_g = merged.capture_system_prompt) != null ? _g : false,
794
+ eventMetadata,
795
+ localWorkshopUrl: resolveLocalWorkshopUrl(merged.local_workshop_url)
796
+ };
797
+ }
798
+ function resolveLocalWorkshopUrl(fileValue) {
799
+ const envValue = process.env["RAINDROP_LOCAL_WORKSHOP_URL"];
800
+ if (envValue !== void 0) {
801
+ if (envValue === "" || envValue.toLowerCase() === "null" || envValue.toLowerCase() === "false") {
802
+ return null;
803
+ }
804
+ return envValue;
805
+ }
806
+ return fileValue;
807
+ }
808
+
809
+ // package.json
810
+ var package_default = {
811
+ name: "@raindrop-ai/opencode-plugin",
812
+ version: "0.0.12",
813
+ description: "Raindrop observability plugin for OpenCode \u2014 automatic session/event/span tracing",
814
+ type: "module",
815
+ main: "dist/index.js",
816
+ module: "dist/index.js",
817
+ types: "dist/index.d.ts",
818
+ license: "MIT",
819
+ repository: {
820
+ type: "git",
821
+ url: "git+https://github.com/raindrop-ai/raindrop-js.git",
822
+ directory: "packages/opencode-plugin"
823
+ },
824
+ homepage: "https://github.com/raindrop-ai/raindrop-js/tree/main/packages/opencode-plugin#readme",
825
+ bugs: {
826
+ url: "https://github.com/raindrop-ai/raindrop-js/issues"
827
+ },
828
+ exports: {
829
+ ".": {
830
+ types: "./dist/index.d.ts",
831
+ import: "./dist/index.js",
832
+ require: "./dist/index.cjs"
833
+ }
834
+ },
835
+ sideEffects: false,
836
+ files: [
837
+ "dist/**"
838
+ ],
839
+ scripts: {
840
+ build: "tsup",
841
+ dev: "tsup --watch",
842
+ clean: "rm -rf dist",
843
+ test: "cd tests && pnpm test"
844
+ },
845
+ peerDependencies: {
846
+ "@opencode-ai/plugin": ">=1.3.0",
847
+ "@opencode-ai/sdk": ">=1.3.0"
848
+ },
849
+ peerDependenciesMeta: {
850
+ "@opencode-ai/sdk": {
851
+ optional: true
852
+ }
853
+ },
854
+ devDependencies: {
855
+ "@raindrop-ai/core": "workspace:*",
856
+ "@opencode-ai/plugin": "^1.3.3",
857
+ "@opencode-ai/sdk": "^1.3.3",
858
+ "@types/node": "^20.11.17",
859
+ tsup: "^8.4.0",
860
+ typescript: "^5.3.3"
861
+ },
862
+ tsup: {
863
+ entry: [
864
+ "src/index.ts"
865
+ ],
866
+ format: [
867
+ "cjs",
868
+ "esm"
869
+ ],
870
+ dts: {
871
+ resolve: true
872
+ },
873
+ clean: true,
874
+ noExternal: [
875
+ "@raindrop-ai/core"
876
+ ]
877
+ },
878
+ publishConfig: {
879
+ access: "public"
880
+ }
881
+ };
882
+
883
+ // src/package-info.ts
884
+ var PLUGIN_NAME = package_default.name;
885
+ var PLUGIN_VERSION = package_default.version;
886
+
792
887
  // src/shipper.ts
793
888
  var EventShipper2 = class extends EventShipper {
794
889
  constructor(opts) {
@@ -959,6 +1054,19 @@ function endPendingToolSpanWithError(state, callID, tool, toolCallArgs, errorMes
959
1054
  }
960
1055
  return true;
961
1056
  }
1057
+ function unwrapQuotes(s) {
1058
+ if (s.length < 2) return s;
1059
+ const first = s[0];
1060
+ const last = s[s.length - 1];
1061
+ if ((first === '"' || first === "'") && first === last) return s.slice(1, -1);
1062
+ return s;
1063
+ }
1064
+ function buildPromptMessages(systemPrompt, userInput) {
1065
+ const messages = [];
1066
+ if (systemPrompt) messages.push({ role: "system", content: systemPrompt });
1067
+ messages.push({ role: "user", content: unwrapQuotes(userInput) });
1068
+ return JSON.stringify(messages);
1069
+ }
962
1070
  function getHostname() {
963
1071
  var _a;
964
1072
  try {
@@ -1184,9 +1292,14 @@ type: ${name != null ? name : "UnknownError"}`;
1184
1292
  const rootSpan = state.currentRootSpan;
1185
1293
  const isCompaction = state.isCompacting === true;
1186
1294
  if (state.parentId) {
1295
+ const userInput = unwrapQuotes(state.currentInput);
1187
1296
  const llmAttrs = [
1188
1297
  attrString("ai.operationId", "generateText"),
1189
- attrString("ai.prompt", state.currentInput),
1298
+ attrString("ai.prompt", userInput),
1299
+ attrString(
1300
+ "ai.prompt.messages",
1301
+ buildPromptMessages(state.currentSystemPrompt, state.currentInput)
1302
+ ),
1190
1303
  attrString("ai.response.text", outputText),
1191
1304
  attrString("gen_ai.system", providerID),
1192
1305
  attrString("gen_ai.request.model", modelID),
@@ -1231,9 +1344,14 @@ type: ${name != null ? name : "UnknownError"}`;
1231
1344
  });
1232
1345
  } else {
1233
1346
  rootSpan.name = isCompaction ? "ai.compaction" : modelName;
1347
+ const userInput = unwrapQuotes(state.currentInput);
1234
1348
  const rootAttrs = [
1235
1349
  attrString("ai.operationId", "generateText"),
1236
- attrString("ai.prompt", state.currentInput),
1350
+ attrString("ai.prompt", userInput),
1351
+ attrString(
1352
+ "ai.prompt.messages",
1353
+ buildPromptMessages(state.currentSystemPrompt, state.currentInput)
1354
+ ),
1237
1355
  attrString("ai.response.text", outputText),
1238
1356
  attrString("gen_ai.system", providerID),
1239
1357
  attrString("gen_ai.request.model", modelID),
@@ -1624,25 +1742,33 @@ async function plugin(input) {
1624
1742
  console.log(`[raindrop-ai/opencode-plugin] [${level}] ${message}`);
1625
1743
  }
1626
1744
  appLog("info", `Loading ${PLUGIN_NAME} v${PLUGIN_VERSION}`);
1627
- if (!config.writeKey) {
1745
+ const resolvedLocalUrl = resolveLocalDebuggerBaseUrl(config.localWorkshopUrl);
1746
+ const hasLocalDestination = resolvedLocalUrl !== null;
1747
+ if (!config.writeKey && !hasLocalDestination) {
1628
1748
  appLog(
1629
1749
  "warn",
1630
- "RAINDROP_WRITE_KEY not set \u2014 Raindrop tracing disabled. Set it in your environment or in ~/.config/opencode/raindrop.json."
1750
+ "RAINDROP_WRITE_KEY not set and no local Workshop daemon detected \u2014 Raindrop tracing disabled. Set RAINDROP_WRITE_KEY for cloud, or RAINDROP_LOCAL_WORKSHOP_URL / RAINDROP_LOCAL_DEBUGGER for local-only mode."
1631
1751
  );
1632
1752
  return {};
1633
1753
  }
1634
1754
  if (config.debug) {
1635
- appLog("info", `Raindrop tracing enabled (endpoint: ${config.endpoint})`);
1755
+ const destinations = [
1756
+ config.writeKey ? `cloud (${config.endpoint})` : null,
1757
+ resolvedLocalUrl ? `local Workshop (${resolvedLocalUrl})` : null
1758
+ ].filter(Boolean);
1759
+ appLog("info", `Raindrop tracing enabled \u2014 destinations: ${destinations.join(", ")}`);
1636
1760
  }
1637
1761
  const eventShipper = new EventShipper2({
1638
1762
  writeKey: config.writeKey,
1639
1763
  endpoint: config.endpoint,
1640
- debug: config.debug
1764
+ debug: config.debug,
1765
+ localDebuggerUrl: config.localWorkshopUrl
1641
1766
  });
1642
1767
  const traceShipper = new TraceShipper2({
1643
1768
  writeKey: config.writeKey,
1644
1769
  endpoint: config.endpoint,
1645
- debug: config.debug
1770
+ debug: config.debug,
1771
+ localDebuggerUrl: config.localWorkshopUrl
1646
1772
  });
1647
1773
  const worktree = (_a = input.worktree) != null ? _a : input.directory;
1648
1774
  const hooks = createHooks(
package/dist/index.js CHANGED
@@ -5,124 +5,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
5
5
  throw Error('Dynamic require of "' + x + '" is not supported');
6
6
  });
7
7
 
8
- // src/config.ts
9
- import { existsSync, readFileSync } from "fs";
10
- import { homedir } from "os";
11
- import { join } from "path";
12
- function loadConfig(projectDirectory) {
13
- var _a, _b, _c, _d, _e, _f, _g;
14
- let merged = {};
15
- const configPaths = [
16
- join(homedir(), ".config", "opencode", "raindrop.json"),
17
- join(projectDirectory, ".opencode", "raindrop.json")
18
- ];
19
- for (const configPath of configPaths) {
20
- try {
21
- if (existsSync(configPath)) {
22
- const content = readFileSync(configPath, "utf-8");
23
- const parsed = JSON.parse(content);
24
- merged = { ...merged, ...parsed };
25
- }
26
- } catch (e) {
27
- }
28
- }
29
- let eventMetadata;
30
- const envMeta = process.env["RAINDROP_EVENT_METADATA"];
31
- if (envMeta) {
32
- try {
33
- eventMetadata = JSON.parse(envMeta);
34
- } catch (e) {
35
- }
36
- }
37
- return {
38
- writeKey: (_b = (_a = process.env["RAINDROP_WRITE_KEY"]) != null ? _a : merged.write_key) != null ? _b : "",
39
- endpoint: (_d = (_c = process.env["RAINDROP_API_URL"]) != null ? _c : merged.api_url) != null ? _d : "https://api.raindrop.ai/v1",
40
- eventName: (_e = merged.event_name) != null ? _e : "opencode_session",
41
- debug: process.env["RAINDROP_DEBUG"] === "true" ? true : (_f = merged.debug) != null ? _f : false,
42
- captureSystemPrompt: process.env["RAINDROP_CAPTURE_SYSTEM_PROMPT"] !== void 0 ? process.env["RAINDROP_CAPTURE_SYSTEM_PROMPT"] === "true" : (_g = merged.capture_system_prompt) != null ? _g : false,
43
- eventMetadata
44
- };
45
- }
46
-
47
- // package.json
48
- var package_default = {
49
- name: "@raindrop-ai/opencode-plugin",
50
- version: "0.0.11",
51
- description: "Raindrop observability plugin for OpenCode \u2014 automatic session/event/span tracing",
52
- type: "module",
53
- main: "dist/index.js",
54
- module: "dist/index.js",
55
- types: "dist/index.d.ts",
56
- license: "MIT",
57
- repository: {
58
- type: "git",
59
- url: "git+https://github.com/raindrop-ai/raindrop-js.git",
60
- directory: "packages/opencode-plugin"
61
- },
62
- homepage: "https://github.com/raindrop-ai/raindrop-js/tree/main/packages/opencode-plugin#readme",
63
- bugs: {
64
- url: "https://github.com/raindrop-ai/raindrop-js/issues"
65
- },
66
- exports: {
67
- ".": {
68
- types: "./dist/index.d.ts",
69
- import: "./dist/index.js",
70
- require: "./dist/index.cjs"
71
- }
72
- },
73
- sideEffects: false,
74
- files: [
75
- "dist/**"
76
- ],
77
- scripts: {
78
- build: "tsup",
79
- dev: "tsup --watch",
80
- clean: "rm -rf dist",
81
- test: "cd tests && pnpm test"
82
- },
83
- peerDependencies: {
84
- "@opencode-ai/plugin": ">=1.3.0",
85
- "@opencode-ai/sdk": ">=1.3.0"
86
- },
87
- peerDependenciesMeta: {
88
- "@opencode-ai/sdk": {
89
- optional: true
90
- }
91
- },
92
- devDependencies: {
93
- "@raindrop-ai/core": "workspace:*",
94
- "@opencode-ai/plugin": "^1.3.3",
95
- "@opencode-ai/sdk": "^1.3.3",
96
- "@types/node": "^20.11.17",
97
- tsup: "^8.4.0",
98
- typescript: "^5.3.3"
99
- },
100
- tsup: {
101
- entry: [
102
- "src/index.ts"
103
- ],
104
- format: [
105
- "cjs",
106
- "esm"
107
- ],
108
- dts: {
109
- resolve: true
110
- },
111
- clean: true,
112
- noExternal: [
113
- "@raindrop-ai/core"
114
- ]
115
- },
116
- publishConfig: {
117
- access: "public"
118
- }
119
- };
120
-
121
- // src/package-info.ts
122
- var PLUGIN_NAME = package_default.name;
123
- var PLUGIN_VERSION = package_default.version;
124
-
125
- // ../core/dist/chunk-4UCYIEH4.js
8
+ // ../core/dist/chunk-OLYXZCOK.js
126
9
  function getCrypto() {
127
10
  const c = globalThis.crypto;
128
11
  return c;
@@ -315,6 +198,91 @@ function buildExportTraceServiceRequest(spans, serviceName = "raindrop.core", se
315
198
  ]
316
199
  };
317
200
  }
201
+ var LOCAL_DEBUGGER_ENV_VAR = "RAINDROP_LOCAL_DEBUGGER";
202
+ var WORKSHOP_ENV_VAR = "RAINDROP_WORKSHOP";
203
+ var DEFAULT_LOCAL_WORKSHOP_URL = "http://localhost:5899/v1/";
204
+ function readEnvVar(name) {
205
+ var _a;
206
+ try {
207
+ const env = (_a = globalThis == null ? void 0 : globalThis.process) == null ? void 0 : _a.env;
208
+ if (env && typeof env[name] === "string" && env[name].length > 0) {
209
+ return env[name];
210
+ }
211
+ } catch (e) {
212
+ }
213
+ return void 0;
214
+ }
215
+ function readWorkshopEnv() {
216
+ const raw = readEnvVar(WORKSHOP_ENV_VAR);
217
+ if (raw === void 0) return void 0;
218
+ const trimmed = raw.trim();
219
+ if (trimmed.length === 0) return void 0;
220
+ if (/^https?:\/\//i.test(trimmed)) return { url: trimmed };
221
+ if (/^(1|true|yes|on)$/i.test(trimmed)) return "enable";
222
+ if (/^(0|false|no|off)$/i.test(trimmed)) return "disable";
223
+ return void 0;
224
+ }
225
+ function isLocalDevHost(hostname) {
226
+ if (!hostname) return false;
227
+ if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "0.0.0.0" || hostname === "::1") {
228
+ return true;
229
+ }
230
+ if (hostname.endsWith(".localhost")) return true;
231
+ return false;
232
+ }
233
+ function readRuntimeHostname() {
234
+ try {
235
+ const loc = globalThis == null ? void 0 : globalThis.location;
236
+ if (loc && typeof loc.hostname === "string" && loc.hostname.length > 0) {
237
+ return loc.hostname;
238
+ }
239
+ } catch (e) {
240
+ }
241
+ return void 0;
242
+ }
243
+ function shouldAutoEnableLocalWorkshop() {
244
+ if (isLocalDevHost(readRuntimeHostname())) return true;
245
+ if (readEnvVar("NODE_ENV") === "development") return true;
246
+ return false;
247
+ }
248
+ function resolveLocalDebuggerBaseUrl(baseUrl) {
249
+ var _a, _b, _c;
250
+ if (baseUrl === null) return null;
251
+ if (typeof baseUrl === "string" && baseUrl.length > 0) {
252
+ return (_a = formatEndpoint(baseUrl)) != null ? _a : null;
253
+ }
254
+ const explicitUrlEnv = readEnvVar(LOCAL_DEBUGGER_ENV_VAR);
255
+ if (explicitUrlEnv) return (_b = formatEndpoint(explicitUrlEnv)) != null ? _b : null;
256
+ const workshopEnv = readWorkshopEnv();
257
+ if (workshopEnv === "disable") return null;
258
+ if (workshopEnv === "enable") return DEFAULT_LOCAL_WORKSHOP_URL;
259
+ if (workshopEnv && "url" in workshopEnv) return (_c = formatEndpoint(workshopEnv.url)) != null ? _c : null;
260
+ if (shouldAutoEnableLocalWorkshop()) return DEFAULT_LOCAL_WORKSHOP_URL;
261
+ return null;
262
+ }
263
+ function mirrorTraceExportToLocalDebugger(body, options = {}) {
264
+ var _a;
265
+ const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
266
+ if (!baseUrl) return;
267
+ void postJson(`${baseUrl}traces`, body, {}, {
268
+ maxAttempts: 1,
269
+ debug: (_a = options.debug) != null ? _a : false,
270
+ sdkName: options.sdkName
271
+ }).catch(() => {
272
+ });
273
+ }
274
+ function mirrorPartialEventToLocalDebugger(event, options = {}) {
275
+ var _a;
276
+ const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
277
+ if (!baseUrl) return;
278
+ const headers = options.writeKey ? { Authorization: `Bearer ${options.writeKey}` } : {};
279
+ void postJson(`${baseUrl}events/track_partial`, event, headers, {
280
+ maxAttempts: 1,
281
+ debug: (_a = options.debug) != null ? _a : false,
282
+ sdkName: options.sdkName
283
+ }).catch(() => {
284
+ });
285
+ }
318
286
  function mergePatches(target, source) {
319
287
  var _a, _b, _c, _d;
320
288
  const out = { ...target, ...source };
@@ -332,7 +300,7 @@ var EventShipper = class {
332
300
  this.sticky = /* @__PURE__ */ new Map();
333
301
  this.timers = /* @__PURE__ */ new Map();
334
302
  this.inFlight = /* @__PURE__ */ new Set();
335
- var _a, _b, _c, _d, _e, _f, _g;
303
+ var _a, _b, _c, _d, _e, _f, _g, _h;
336
304
  this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
337
305
  this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
338
306
  this.enabled = opts.enabled !== false;
@@ -341,11 +309,15 @@ var EventShipper = class {
341
309
  this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
342
310
  this.prefix = `[raindrop-ai/${this.sdkName}]`;
343
311
  this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
312
+ this.localDebuggerUrl = (_f = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _f : void 0;
313
+ if (this.debug && this.localDebuggerUrl) {
314
+ console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
315
+ }
344
316
  const isNode = typeof process !== "undefined" && typeof process.version === "string";
345
317
  this.context = {
346
318
  library: {
347
- name: (_f = opts.libraryName) != null ? _f : "@raindrop-ai/core",
348
- version: (_g = opts.libraryVersion) != null ? _g : "0.0.0"
319
+ name: (_g = opts.libraryName) != null ? _g : "@raindrop-ai/core",
320
+ version: (_h = opts.libraryVersion) != null ? _h : "0.0.0"
349
321
  },
350
322
  metadata: {
351
323
  jsRuntime: isNode ? "node" : "web",
@@ -431,6 +403,7 @@ var EventShipper = class {
431
403
  }
432
404
  }
433
405
  ];
406
+ if (!this.writeKey) return;
434
407
  const url = `${this.baseUrl}signals/track`;
435
408
  try {
436
409
  await postJson(url, body, this.authHeaders(), {
@@ -461,6 +434,7 @@ var EventShipper = class {
461
434
  traits: (_a = user.traits) != null ? _a : {}
462
435
  };
463
436
  });
437
+ if (!this.writeKey) return;
464
438
  if (body.length === 0) return;
465
439
  const url = `${this.baseUrl}users/identify`;
466
440
  try {
@@ -537,6 +511,18 @@ var EventShipper = class {
537
511
  endpoint: url
538
512
  });
539
513
  }
514
+ if (this.localDebuggerUrl) {
515
+ mirrorPartialEventToLocalDebugger(payload, {
516
+ baseUrl: this.localDebuggerUrl,
517
+ writeKey: this.writeKey,
518
+ debug: this.debug,
519
+ sdkName: this.sdkName
520
+ });
521
+ }
522
+ if (!this.writeKey) {
523
+ if (!isPending) this.sticky.delete(eventId);
524
+ return;
525
+ }
540
526
  const p = postJson(url, payload, this.authHeaders(), {
541
527
  maxAttempts: 3,
542
528
  debug: this.debug,
@@ -561,28 +547,11 @@ var EventShipper = class {
561
547
  }
562
548
  }
563
549
  };
564
- var LOCAL_DEBUGGER_ENV_VAR = "RAINDROP_LOCAL_DEBUGGER";
565
- function resolveLocalDebuggerBaseUrl(baseUrl) {
566
- var _a, _b, _c;
567
- const resolved = (_b = baseUrl != null ? baseUrl : typeof process !== "undefined" ? (_a = process.env) == null ? void 0 : _a[LOCAL_DEBUGGER_ENV_VAR] : void 0) != null ? _b : null;
568
- return resolved ? (_c = formatEndpoint(resolved)) != null ? _c : null : null;
569
- }
570
- function mirrorTraceExportToLocalDebugger(body, options = {}) {
571
- var _a;
572
- const baseUrl = resolveLocalDebuggerBaseUrl(options.baseUrl);
573
- if (!baseUrl) return;
574
- void postJson(`${baseUrl}traces`, body, {}, {
575
- maxAttempts: 1,
576
- debug: (_a = options.debug) != null ? _a : false,
577
- sdkName: options.sdkName
578
- }).catch(() => {
579
- });
580
- }
581
550
  var TraceShipper = class {
582
551
  constructor(opts) {
583
552
  this.queue = [];
584
553
  this.inFlight = /* @__PURE__ */ new Set();
585
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
554
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
586
555
  this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
587
556
  this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
588
557
  this.enabled = opts.enabled !== false;
@@ -595,12 +564,9 @@ var TraceShipper = class {
595
564
  this.prefix = `[raindrop-ai/${this.sdkName}]`;
596
565
  this.serviceName = (_g = opts.serviceName) != null ? _g : "raindrop.core";
597
566
  this.serviceVersion = (_h = opts.serviceVersion) != null ? _h : "0.0.0";
598
- const localDebugger = typeof process !== "undefined" ? (_i = process.env) == null ? void 0 : _i.RAINDROP_LOCAL_DEBUGGER : void 0;
599
- if (localDebugger) {
600
- this.localDebuggerUrl = (_j = resolveLocalDebuggerBaseUrl(localDebugger)) != null ? _j : void 0;
601
- if (this.debug) {
602
- console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
603
- }
567
+ this.localDebuggerUrl = (_i = resolveLocalDebuggerBaseUrl(opts.localDebuggerUrl)) != null ? _i : void 0;
568
+ if (this.debug && this.localDebuggerUrl) {
569
+ console.log(`${this.prefix} Local debugger mirroring: ${this.localDebuggerUrl}`);
604
570
  }
605
571
  }
606
572
  isDebugEnabled() {
@@ -728,6 +694,7 @@ var TraceShipper = class {
728
694
  }
729
695
  while (this.queue.length > 0) {
730
696
  const batch = this.queue.splice(0, this.maxBatchSize);
697
+ if (!this.writeKey) continue;
731
698
  const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
732
699
  const url = `${this.baseUrl}traces`;
733
700
  if (this.debug) {
@@ -770,6 +737,134 @@ var TraceShipper = class {
770
737
  import { AsyncLocalStorage } from "async_hooks";
771
738
  globalThis.RAINDROP_ASYNC_LOCAL_STORAGE = AsyncLocalStorage;
772
739
 
740
+ // src/config.ts
741
+ import { existsSync, readFileSync } from "fs";
742
+ import { homedir } from "os";
743
+ import { join } from "path";
744
+ function loadConfig(projectDirectory) {
745
+ var _a, _b, _c, _d, _e, _f, _g;
746
+ let merged = {};
747
+ const configPaths = [
748
+ join(homedir(), ".config", "opencode", "raindrop.json"),
749
+ join(projectDirectory, ".opencode", "raindrop.json")
750
+ ];
751
+ for (const configPath of configPaths) {
752
+ try {
753
+ if (existsSync(configPath)) {
754
+ const content = readFileSync(configPath, "utf-8");
755
+ const parsed = JSON.parse(content);
756
+ merged = { ...merged, ...parsed };
757
+ }
758
+ } catch (e) {
759
+ }
760
+ }
761
+ let eventMetadata;
762
+ const envMeta = process.env["RAINDROP_EVENT_METADATA"];
763
+ if (envMeta) {
764
+ try {
765
+ eventMetadata = JSON.parse(envMeta);
766
+ } catch (e) {
767
+ }
768
+ }
769
+ return {
770
+ writeKey: (_b = (_a = process.env["RAINDROP_WRITE_KEY"]) != null ? _a : merged.write_key) != null ? _b : "",
771
+ endpoint: (_d = (_c = process.env["RAINDROP_API_URL"]) != null ? _c : merged.api_url) != null ? _d : "https://api.raindrop.ai/v1",
772
+ eventName: (_e = merged.event_name) != null ? _e : "opencode_session",
773
+ debug: process.env["RAINDROP_DEBUG"] === "true" ? true : (_f = merged.debug) != null ? _f : false,
774
+ captureSystemPrompt: process.env["RAINDROP_CAPTURE_SYSTEM_PROMPT"] !== void 0 ? process.env["RAINDROP_CAPTURE_SYSTEM_PROMPT"] === "true" : (_g = merged.capture_system_prompt) != null ? _g : false,
775
+ eventMetadata,
776
+ localWorkshopUrl: resolveLocalWorkshopUrl(merged.local_workshop_url)
777
+ };
778
+ }
779
+ function resolveLocalWorkshopUrl(fileValue) {
780
+ const envValue = process.env["RAINDROP_LOCAL_WORKSHOP_URL"];
781
+ if (envValue !== void 0) {
782
+ if (envValue === "" || envValue.toLowerCase() === "null" || envValue.toLowerCase() === "false") {
783
+ return null;
784
+ }
785
+ return envValue;
786
+ }
787
+ return fileValue;
788
+ }
789
+
790
+ // package.json
791
+ var package_default = {
792
+ name: "@raindrop-ai/opencode-plugin",
793
+ version: "0.0.12",
794
+ description: "Raindrop observability plugin for OpenCode \u2014 automatic session/event/span tracing",
795
+ type: "module",
796
+ main: "dist/index.js",
797
+ module: "dist/index.js",
798
+ types: "dist/index.d.ts",
799
+ license: "MIT",
800
+ repository: {
801
+ type: "git",
802
+ url: "git+https://github.com/raindrop-ai/raindrop-js.git",
803
+ directory: "packages/opencode-plugin"
804
+ },
805
+ homepage: "https://github.com/raindrop-ai/raindrop-js/tree/main/packages/opencode-plugin#readme",
806
+ bugs: {
807
+ url: "https://github.com/raindrop-ai/raindrop-js/issues"
808
+ },
809
+ exports: {
810
+ ".": {
811
+ types: "./dist/index.d.ts",
812
+ import: "./dist/index.js",
813
+ require: "./dist/index.cjs"
814
+ }
815
+ },
816
+ sideEffects: false,
817
+ files: [
818
+ "dist/**"
819
+ ],
820
+ scripts: {
821
+ build: "tsup",
822
+ dev: "tsup --watch",
823
+ clean: "rm -rf dist",
824
+ test: "cd tests && pnpm test"
825
+ },
826
+ peerDependencies: {
827
+ "@opencode-ai/plugin": ">=1.3.0",
828
+ "@opencode-ai/sdk": ">=1.3.0"
829
+ },
830
+ peerDependenciesMeta: {
831
+ "@opencode-ai/sdk": {
832
+ optional: true
833
+ }
834
+ },
835
+ devDependencies: {
836
+ "@raindrop-ai/core": "workspace:*",
837
+ "@opencode-ai/plugin": "^1.3.3",
838
+ "@opencode-ai/sdk": "^1.3.3",
839
+ "@types/node": "^20.11.17",
840
+ tsup: "^8.4.0",
841
+ typescript: "^5.3.3"
842
+ },
843
+ tsup: {
844
+ entry: [
845
+ "src/index.ts"
846
+ ],
847
+ format: [
848
+ "cjs",
849
+ "esm"
850
+ ],
851
+ dts: {
852
+ resolve: true
853
+ },
854
+ clean: true,
855
+ noExternal: [
856
+ "@raindrop-ai/core"
857
+ ]
858
+ },
859
+ publishConfig: {
860
+ access: "public"
861
+ }
862
+ };
863
+
864
+ // src/package-info.ts
865
+ var PLUGIN_NAME = package_default.name;
866
+ var PLUGIN_VERSION = package_default.version;
867
+
773
868
  // src/shipper.ts
774
869
  var EventShipper2 = class extends EventShipper {
775
870
  constructor(opts) {
@@ -940,6 +1035,19 @@ function endPendingToolSpanWithError(state, callID, tool, toolCallArgs, errorMes
940
1035
  }
941
1036
  return true;
942
1037
  }
1038
+ function unwrapQuotes(s) {
1039
+ if (s.length < 2) return s;
1040
+ const first = s[0];
1041
+ const last = s[s.length - 1];
1042
+ if ((first === '"' || first === "'") && first === last) return s.slice(1, -1);
1043
+ return s;
1044
+ }
1045
+ function buildPromptMessages(systemPrompt, userInput) {
1046
+ const messages = [];
1047
+ if (systemPrompt) messages.push({ role: "system", content: systemPrompt });
1048
+ messages.push({ role: "user", content: unwrapQuotes(userInput) });
1049
+ return JSON.stringify(messages);
1050
+ }
943
1051
  function getHostname() {
944
1052
  var _a;
945
1053
  try {
@@ -1165,9 +1273,14 @@ type: ${name != null ? name : "UnknownError"}`;
1165
1273
  const rootSpan = state.currentRootSpan;
1166
1274
  const isCompaction = state.isCompacting === true;
1167
1275
  if (state.parentId) {
1276
+ const userInput = unwrapQuotes(state.currentInput);
1168
1277
  const llmAttrs = [
1169
1278
  attrString("ai.operationId", "generateText"),
1170
- attrString("ai.prompt", state.currentInput),
1279
+ attrString("ai.prompt", userInput),
1280
+ attrString(
1281
+ "ai.prompt.messages",
1282
+ buildPromptMessages(state.currentSystemPrompt, state.currentInput)
1283
+ ),
1171
1284
  attrString("ai.response.text", outputText),
1172
1285
  attrString("gen_ai.system", providerID),
1173
1286
  attrString("gen_ai.request.model", modelID),
@@ -1212,9 +1325,14 @@ type: ${name != null ? name : "UnknownError"}`;
1212
1325
  });
1213
1326
  } else {
1214
1327
  rootSpan.name = isCompaction ? "ai.compaction" : modelName;
1328
+ const userInput = unwrapQuotes(state.currentInput);
1215
1329
  const rootAttrs = [
1216
1330
  attrString("ai.operationId", "generateText"),
1217
- attrString("ai.prompt", state.currentInput),
1331
+ attrString("ai.prompt", userInput),
1332
+ attrString(
1333
+ "ai.prompt.messages",
1334
+ buildPromptMessages(state.currentSystemPrompt, state.currentInput)
1335
+ ),
1218
1336
  attrString("ai.response.text", outputText),
1219
1337
  attrString("gen_ai.system", providerID),
1220
1338
  attrString("gen_ai.request.model", modelID),
@@ -1605,25 +1723,33 @@ async function plugin(input) {
1605
1723
  console.log(`[raindrop-ai/opencode-plugin] [${level}] ${message}`);
1606
1724
  }
1607
1725
  appLog("info", `Loading ${PLUGIN_NAME} v${PLUGIN_VERSION}`);
1608
- if (!config.writeKey) {
1726
+ const resolvedLocalUrl = resolveLocalDebuggerBaseUrl(config.localWorkshopUrl);
1727
+ const hasLocalDestination = resolvedLocalUrl !== null;
1728
+ if (!config.writeKey && !hasLocalDestination) {
1609
1729
  appLog(
1610
1730
  "warn",
1611
- "RAINDROP_WRITE_KEY not set \u2014 Raindrop tracing disabled. Set it in your environment or in ~/.config/opencode/raindrop.json."
1731
+ "RAINDROP_WRITE_KEY not set and no local Workshop daemon detected \u2014 Raindrop tracing disabled. Set RAINDROP_WRITE_KEY for cloud, or RAINDROP_LOCAL_WORKSHOP_URL / RAINDROP_LOCAL_DEBUGGER for local-only mode."
1612
1732
  );
1613
1733
  return {};
1614
1734
  }
1615
1735
  if (config.debug) {
1616
- appLog("info", `Raindrop tracing enabled (endpoint: ${config.endpoint})`);
1736
+ const destinations = [
1737
+ config.writeKey ? `cloud (${config.endpoint})` : null,
1738
+ resolvedLocalUrl ? `local Workshop (${resolvedLocalUrl})` : null
1739
+ ].filter(Boolean);
1740
+ appLog("info", `Raindrop tracing enabled \u2014 destinations: ${destinations.join(", ")}`);
1617
1741
  }
1618
1742
  const eventShipper = new EventShipper2({
1619
1743
  writeKey: config.writeKey,
1620
1744
  endpoint: config.endpoint,
1621
- debug: config.debug
1745
+ debug: config.debug,
1746
+ localDebuggerUrl: config.localWorkshopUrl
1622
1747
  });
1623
1748
  const traceShipper = new TraceShipper2({
1624
1749
  writeKey: config.writeKey,
1625
1750
  endpoint: config.endpoint,
1626
- debug: config.debug
1751
+ debug: config.debug,
1752
+ localDebuggerUrl: config.localWorkshopUrl
1627
1753
  });
1628
1754
  const worktree = (_a = input.worktree) != null ? _a : input.directory;
1629
1755
  const hooks = createHooks(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raindrop-ai/opencode-plugin",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
4
4
  "description": "Raindrop observability plugin for OpenCode — automatic session/event/span tracing",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",