@raindrop-ai/opencode-plugin 0.0.10 → 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 +343 -163
  2. package/dist/index.js +343 -163
  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.10",
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) {
@@ -919,6 +1014,59 @@ function createSessionState(sessionId) {
919
1014
  function callKey(sessionId, callId) {
920
1015
  return `${sessionId}:${callId}`;
921
1016
  }
1017
+ function markTaskCallFinished(sessionID, callID) {
1018
+ const running = runningTaskCallsBySession.get(sessionID);
1019
+ if (!running) return;
1020
+ running.delete(callID);
1021
+ if (running.size === 0) runningTaskCallsBySession.delete(sessionID);
1022
+ }
1023
+ function endPendingToolSpanWithError(state, callID, tool, toolCallArgs, errorMessage, traceShipper) {
1024
+ const startInfo = state.toolSpanStarts.get(callID);
1025
+ if (!startInfo) return false;
1026
+ state.toolSpanStarts.delete(callID);
1027
+ const error = new Error(errorMessage);
1028
+ const endAttrs = [
1029
+ attrString("ai.toolCall.args", toolCallArgs),
1030
+ attrString("error.message", errorMessage)
1031
+ ];
1032
+ if (startInfo.liveSpan) {
1033
+ traceShipper.endSpan(startInfo.liveSpan, {
1034
+ attributes: endAttrs,
1035
+ error
1036
+ });
1037
+ } else {
1038
+ const toolSpan = traceShipper.startSpan({
1039
+ name: "ai.toolCall",
1040
+ parent: startInfo.parent,
1041
+ eventId: startInfo.eventId,
1042
+ startTimeUnixNano: startInfo.startTimeUnixNano,
1043
+ attributes: [
1044
+ attrString("ai.operationId", "ai.toolCall"),
1045
+ attrString("ai.toolCall.name", tool),
1046
+ attrString("ai.toolCall.id", callID)
1047
+ ]
1048
+ });
1049
+ traceShipper.endSpan(toolSpan, {
1050
+ attributes: endAttrs,
1051
+ error,
1052
+ endTimeUnixNano: nowUnixNanoString()
1053
+ });
1054
+ }
1055
+ return true;
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
+ }
922
1070
  function getHostname() {
923
1071
  var _a;
924
1072
  try {
@@ -1003,13 +1151,13 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
1003
1151
  state.outputParts.set(messageId, partObj["text"]);
1004
1152
  } else if (partObj["type"] === "tool" && messageId) {
1005
1153
  const tool = partObj["tool"];
1154
+ const callID = partObj["callID"];
1155
+ const partState = partObj["state"];
1156
+ if (callID == null || callID === "")
1157
+ throw new Error("message.part.updated: part.callID is required for tool part");
1158
+ if (partState == null || typeof partState !== "object")
1159
+ throw new Error("message.part.updated: part.state is required for tool part");
1006
1160
  if (tool === "task") {
1007
- const callID = partObj["callID"];
1008
- const partState = partObj["state"];
1009
- if (callID == null || callID === "")
1010
- throw new Error("message.part.updated: part.callID is required for tool part");
1011
- if (partState == null || typeof partState !== "object")
1012
- throw new Error("message.part.updated: part.state is required for tool part");
1013
1161
  const metadata = partState["metadata"];
1014
1162
  const childSessionId = metadata != null && typeof metadata === "object" ? metadata["sessionId"] : void 0;
1015
1163
  if (childSessionId != null && childSessionId !== "") {
@@ -1024,6 +1172,24 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
1024
1172
  );
1025
1173
  }
1026
1174
  }
1175
+ const toolState = partState;
1176
+ if (toolState["status"] === "error") {
1177
+ const errorMessage = toolState["error"];
1178
+ if (typeof errorMessage !== "string" || errorMessage === "")
1179
+ throw new Error("message.part.updated: error tool state must have error string");
1180
+ const input = toolState["input"];
1181
+ const toolCallArgs = input === void 0 ? void 0 : typeof input === "string" ? input : JSON.stringify(input);
1182
+ if (endPendingToolSpanWithError(
1183
+ state,
1184
+ callID,
1185
+ tool,
1186
+ toolCallArgs,
1187
+ errorMessage,
1188
+ traceShipper
1189
+ ) && tool === "task") {
1190
+ markTaskCallFinished(partSessionID, callID);
1191
+ }
1192
+ }
1027
1193
  } else if (partObj["type"] === "reasoning" && typeof partObj["text"] === "string") {
1028
1194
  state.reasoningParts.set(messageId, partObj["text"]);
1029
1195
  }
@@ -1126,9 +1292,14 @@ type: ${name != null ? name : "UnknownError"}`;
1126
1292
  const rootSpan = state.currentRootSpan;
1127
1293
  const isCompaction = state.isCompacting === true;
1128
1294
  if (state.parentId) {
1295
+ const userInput = unwrapQuotes(state.currentInput);
1129
1296
  const llmAttrs = [
1130
1297
  attrString("ai.operationId", "generateText"),
1131
- attrString("ai.prompt", state.currentInput),
1298
+ attrString("ai.prompt", userInput),
1299
+ attrString(
1300
+ "ai.prompt.messages",
1301
+ buildPromptMessages(state.currentSystemPrompt, state.currentInput)
1302
+ ),
1132
1303
  attrString("ai.response.text", outputText),
1133
1304
  attrString("gen_ai.system", providerID),
1134
1305
  attrString("gen_ai.request.model", modelID),
@@ -1173,9 +1344,14 @@ type: ${name != null ? name : "UnknownError"}`;
1173
1344
  });
1174
1345
  } else {
1175
1346
  rootSpan.name = isCompaction ? "ai.compaction" : modelName;
1347
+ const userInput = unwrapQuotes(state.currentInput);
1176
1348
  const rootAttrs = [
1177
1349
  attrString("ai.operationId", "generateText"),
1178
- attrString("ai.prompt", state.currentInput),
1350
+ attrString("ai.prompt", userInput),
1351
+ attrString(
1352
+ "ai.prompt.messages",
1353
+ buildPromptMessages(state.currentSystemPrompt, state.currentInput)
1354
+ ),
1179
1355
  attrString("ai.response.text", outputText),
1180
1356
  attrString("gen_ai.system", providerID),
1181
1357
  attrString("gen_ai.request.model", modelID),
@@ -1507,11 +1683,7 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
1507
1683
  }
1508
1684
  }
1509
1685
  if (tool === "task") {
1510
- const running = runningTaskCallsBySession.get(sessionID);
1511
- if (running) {
1512
- running.delete(callID);
1513
- if (running.size === 0) runningTaskCallsBySession.delete(sessionID);
1514
- }
1686
+ markTaskCallFinished(sessionID, callID);
1515
1687
  }
1516
1688
  } catch (err) {
1517
1689
  console.log(
@@ -1570,25 +1742,33 @@ async function plugin(input) {
1570
1742
  console.log(`[raindrop-ai/opencode-plugin] [${level}] ${message}`);
1571
1743
  }
1572
1744
  appLog("info", `Loading ${PLUGIN_NAME} v${PLUGIN_VERSION}`);
1573
- if (!config.writeKey) {
1745
+ const resolvedLocalUrl = resolveLocalDebuggerBaseUrl(config.localWorkshopUrl);
1746
+ const hasLocalDestination = resolvedLocalUrl !== null;
1747
+ if (!config.writeKey && !hasLocalDestination) {
1574
1748
  appLog(
1575
1749
  "warn",
1576
- "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."
1577
1751
  );
1578
1752
  return {};
1579
1753
  }
1580
1754
  if (config.debug) {
1581
- 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(", ")}`);
1582
1760
  }
1583
1761
  const eventShipper = new EventShipper2({
1584
1762
  writeKey: config.writeKey,
1585
1763
  endpoint: config.endpoint,
1586
- debug: config.debug
1764
+ debug: config.debug,
1765
+ localDebuggerUrl: config.localWorkshopUrl
1587
1766
  });
1588
1767
  const traceShipper = new TraceShipper2({
1589
1768
  writeKey: config.writeKey,
1590
1769
  endpoint: config.endpoint,
1591
- debug: config.debug
1770
+ debug: config.debug,
1771
+ localDebuggerUrl: config.localWorkshopUrl
1592
1772
  });
1593
1773
  const worktree = (_a = input.worktree) != null ? _a : input.directory;
1594
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.10",
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) {
@@ -900,6 +995,59 @@ function createSessionState(sessionId) {
900
995
  function callKey(sessionId, callId) {
901
996
  return `${sessionId}:${callId}`;
902
997
  }
998
+ function markTaskCallFinished(sessionID, callID) {
999
+ const running = runningTaskCallsBySession.get(sessionID);
1000
+ if (!running) return;
1001
+ running.delete(callID);
1002
+ if (running.size === 0) runningTaskCallsBySession.delete(sessionID);
1003
+ }
1004
+ function endPendingToolSpanWithError(state, callID, tool, toolCallArgs, errorMessage, traceShipper) {
1005
+ const startInfo = state.toolSpanStarts.get(callID);
1006
+ if (!startInfo) return false;
1007
+ state.toolSpanStarts.delete(callID);
1008
+ const error = new Error(errorMessage);
1009
+ const endAttrs = [
1010
+ attrString("ai.toolCall.args", toolCallArgs),
1011
+ attrString("error.message", errorMessage)
1012
+ ];
1013
+ if (startInfo.liveSpan) {
1014
+ traceShipper.endSpan(startInfo.liveSpan, {
1015
+ attributes: endAttrs,
1016
+ error
1017
+ });
1018
+ } else {
1019
+ const toolSpan = traceShipper.startSpan({
1020
+ name: "ai.toolCall",
1021
+ parent: startInfo.parent,
1022
+ eventId: startInfo.eventId,
1023
+ startTimeUnixNano: startInfo.startTimeUnixNano,
1024
+ attributes: [
1025
+ attrString("ai.operationId", "ai.toolCall"),
1026
+ attrString("ai.toolCall.name", tool),
1027
+ attrString("ai.toolCall.id", callID)
1028
+ ]
1029
+ });
1030
+ traceShipper.endSpan(toolSpan, {
1031
+ attributes: endAttrs,
1032
+ error,
1033
+ endTimeUnixNano: nowUnixNanoString()
1034
+ });
1035
+ }
1036
+ return true;
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
+ }
903
1051
  function getHostname() {
904
1052
  var _a;
905
1053
  try {
@@ -984,13 +1132,13 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
984
1132
  state.outputParts.set(messageId, partObj["text"]);
985
1133
  } else if (partObj["type"] === "tool" && messageId) {
986
1134
  const tool = partObj["tool"];
1135
+ const callID = partObj["callID"];
1136
+ const partState = partObj["state"];
1137
+ if (callID == null || callID === "")
1138
+ throw new Error("message.part.updated: part.callID is required for tool part");
1139
+ if (partState == null || typeof partState !== "object")
1140
+ throw new Error("message.part.updated: part.state is required for tool part");
987
1141
  if (tool === "task") {
988
- const callID = partObj["callID"];
989
- const partState = partObj["state"];
990
- if (callID == null || callID === "")
991
- throw new Error("message.part.updated: part.callID is required for tool part");
992
- if (partState == null || typeof partState !== "object")
993
- throw new Error("message.part.updated: part.state is required for tool part");
994
1142
  const metadata = partState["metadata"];
995
1143
  const childSessionId = metadata != null && typeof metadata === "object" ? metadata["sessionId"] : void 0;
996
1144
  if (childSessionId != null && childSessionId !== "") {
@@ -1005,6 +1153,24 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
1005
1153
  );
1006
1154
  }
1007
1155
  }
1156
+ const toolState = partState;
1157
+ if (toolState["status"] === "error") {
1158
+ const errorMessage = toolState["error"];
1159
+ if (typeof errorMessage !== "string" || errorMessage === "")
1160
+ throw new Error("message.part.updated: error tool state must have error string");
1161
+ const input = toolState["input"];
1162
+ const toolCallArgs = input === void 0 ? void 0 : typeof input === "string" ? input : JSON.stringify(input);
1163
+ if (endPendingToolSpanWithError(
1164
+ state,
1165
+ callID,
1166
+ tool,
1167
+ toolCallArgs,
1168
+ errorMessage,
1169
+ traceShipper
1170
+ ) && tool === "task") {
1171
+ markTaskCallFinished(partSessionID, callID);
1172
+ }
1173
+ }
1008
1174
  } else if (partObj["type"] === "reasoning" && typeof partObj["text"] === "string") {
1009
1175
  state.reasoningParts.set(messageId, partObj["text"]);
1010
1176
  }
@@ -1107,9 +1273,14 @@ type: ${name != null ? name : "UnknownError"}`;
1107
1273
  const rootSpan = state.currentRootSpan;
1108
1274
  const isCompaction = state.isCompacting === true;
1109
1275
  if (state.parentId) {
1276
+ const userInput = unwrapQuotes(state.currentInput);
1110
1277
  const llmAttrs = [
1111
1278
  attrString("ai.operationId", "generateText"),
1112
- attrString("ai.prompt", state.currentInput),
1279
+ attrString("ai.prompt", userInput),
1280
+ attrString(
1281
+ "ai.prompt.messages",
1282
+ buildPromptMessages(state.currentSystemPrompt, state.currentInput)
1283
+ ),
1113
1284
  attrString("ai.response.text", outputText),
1114
1285
  attrString("gen_ai.system", providerID),
1115
1286
  attrString("gen_ai.request.model", modelID),
@@ -1154,9 +1325,14 @@ type: ${name != null ? name : "UnknownError"}`;
1154
1325
  });
1155
1326
  } else {
1156
1327
  rootSpan.name = isCompaction ? "ai.compaction" : modelName;
1328
+ const userInput = unwrapQuotes(state.currentInput);
1157
1329
  const rootAttrs = [
1158
1330
  attrString("ai.operationId", "generateText"),
1159
- attrString("ai.prompt", state.currentInput),
1331
+ attrString("ai.prompt", userInput),
1332
+ attrString(
1333
+ "ai.prompt.messages",
1334
+ buildPromptMessages(state.currentSystemPrompt, state.currentInput)
1335
+ ),
1160
1336
  attrString("ai.response.text", outputText),
1161
1337
  attrString("gen_ai.system", providerID),
1162
1338
  attrString("gen_ai.request.model", modelID),
@@ -1488,11 +1664,7 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
1488
1664
  }
1489
1665
  }
1490
1666
  if (tool === "task") {
1491
- const running = runningTaskCallsBySession.get(sessionID);
1492
- if (running) {
1493
- running.delete(callID);
1494
- if (running.size === 0) runningTaskCallsBySession.delete(sessionID);
1495
- }
1667
+ markTaskCallFinished(sessionID, callID);
1496
1668
  }
1497
1669
  } catch (err) {
1498
1670
  console.log(
@@ -1551,25 +1723,33 @@ async function plugin(input) {
1551
1723
  console.log(`[raindrop-ai/opencode-plugin] [${level}] ${message}`);
1552
1724
  }
1553
1725
  appLog("info", `Loading ${PLUGIN_NAME} v${PLUGIN_VERSION}`);
1554
- if (!config.writeKey) {
1726
+ const resolvedLocalUrl = resolveLocalDebuggerBaseUrl(config.localWorkshopUrl);
1727
+ const hasLocalDestination = resolvedLocalUrl !== null;
1728
+ if (!config.writeKey && !hasLocalDestination) {
1555
1729
  appLog(
1556
1730
  "warn",
1557
- "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."
1558
1732
  );
1559
1733
  return {};
1560
1734
  }
1561
1735
  if (config.debug) {
1562
- 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(", ")}`);
1563
1741
  }
1564
1742
  const eventShipper = new EventShipper2({
1565
1743
  writeKey: config.writeKey,
1566
1744
  endpoint: config.endpoint,
1567
- debug: config.debug
1745
+ debug: config.debug,
1746
+ localDebuggerUrl: config.localWorkshopUrl
1568
1747
  });
1569
1748
  const traceShipper = new TraceShipper2({
1570
1749
  writeKey: config.writeKey,
1571
1750
  endpoint: config.endpoint,
1572
- debug: config.debug
1751
+ debug: config.debug,
1752
+ localDebuggerUrl: config.localWorkshopUrl
1573
1753
  });
1574
1754
  const worktree = (_a = input.worktree) != null ? _a : input.directory;
1575
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.10",
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",