@raindrop-ai/opencode-plugin 0.0.2 → 0.0.4

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 +576 -300
  2. package/dist/index.js +576 -300
  3. package/package.json +3 -2
package/dist/index.cjs CHANGED
@@ -49,12 +49,72 @@ function loadConfig(projectDirectory) {
49
49
  writeKey: (_b = (_a = process.env["RAINDROP_WRITE_KEY"]) != null ? _a : merged.write_key) != null ? _b : "",
50
50
  endpoint: (_d = (_c = process.env["RAINDROP_API_URL"]) != null ? _c : merged.api_url) != null ? _d : "https://api.raindrop.ai/v1",
51
51
  eventName: (_e = merged.event_name) != null ? _e : "opencode_session",
52
- userId: merged.user_id,
53
52
  debug: process.env["RAINDROP_DEBUG"] === "true" ? true : (_f = merged.debug) != null ? _f : false
54
53
  };
55
54
  }
56
55
 
56
+ // package.json
57
+ var package_default = {
58
+ name: "@raindrop-ai/opencode-plugin",
59
+ version: "0.0.4",
60
+ description: "Raindrop observability plugin for OpenCode \u2014 automatic session/event/span tracing",
61
+ type: "module",
62
+ main: "dist/index.cjs",
63
+ module: "dist/index.js",
64
+ types: "dist/index.d.ts",
65
+ exports: {
66
+ ".": {
67
+ types: "./dist/index.d.ts",
68
+ import: "./dist/index.js",
69
+ require: "./dist/index.cjs"
70
+ }
71
+ },
72
+ sideEffects: false,
73
+ files: [
74
+ "dist/**"
75
+ ],
76
+ scripts: {
77
+ build: "tsup",
78
+ dev: "tsup --watch",
79
+ clean: "rm -rf dist",
80
+ test: "cd tests && yarn test"
81
+ },
82
+ peerDependencies: {
83
+ "@opencode-ai/plugin": ">=1.2.0",
84
+ "@opencode-ai/sdk": ">=1.2.0"
85
+ },
86
+ peerDependenciesMeta: {
87
+ "@opencode-ai/sdk": {
88
+ optional: true
89
+ }
90
+ },
91
+ devDependencies: {
92
+ "@opencode-ai/plugin": "^1.2.0",
93
+ "@opencode-ai/sdk": "^1.2.0",
94
+ "@types/node": "^20.11.17",
95
+ tsup: "^8.4.0",
96
+ typescript: "^5.3.3"
97
+ },
98
+ tsup: {
99
+ entry: [
100
+ "src/index.ts"
101
+ ],
102
+ format: [
103
+ "cjs",
104
+ "esm"
105
+ ],
106
+ dts: {
107
+ resolve: true
108
+ },
109
+ clean: true
110
+ },
111
+ publishConfig: {
112
+ access: "public"
113
+ }
114
+ };
115
+
57
116
  // src/shipper.ts
117
+ var PLUGIN_VERSION = package_default.version;
58
118
  function getCryptoObj() {
59
119
  return globalThis.crypto;
60
120
  }
@@ -94,10 +154,7 @@ function base64Encode(bytes) {
94
154
  return out;
95
155
  }
96
156
  var SERVICE_NAME = "raindrop.opencode-plugin";
97
- var PLUGIN_VERSION = "0.1.0";
98
157
  var SpanStatusCode = {
99
- UNSET: 0,
100
- OK: 1,
101
158
  ERROR: 2
102
159
  };
103
160
  function generateId() {
@@ -113,7 +170,7 @@ function createSpanIds(parent) {
113
170
  };
114
171
  }
115
172
  function nowUnixNanoString() {
116
- return String(Date.now() * 1e6);
173
+ return Date.now().toString() + "000000";
117
174
  }
118
175
  function attrString(key, value) {
119
176
  if (value === void 0) return void 0;
@@ -125,7 +182,11 @@ function attrInt(key, value) {
125
182
  return { key, value: { intValue: String(Math.trunc(value)) } };
126
183
  }
127
184
  function buildOtlpSpan(args) {
128
- const attrs = args.attributes.filter((x) => x !== void 0);
185
+ const attrs = [
186
+ attrString("span.id", args.ids.spanIdB64),
187
+ attrString("span.parent.id", args.ids.parentSpanIdB64),
188
+ ...args.attributes
189
+ ].filter((x) => x !== void 0);
129
190
  const span = {
130
191
  traceId: args.ids.traceIdB64,
131
192
  spanId: args.ids.spanIdB64,
@@ -195,9 +256,7 @@ async function withRetry(operation, opName, opts) {
195
256
  }
196
257
  if (delay > 0) await wait(delay);
197
258
  } else if (opts.debug) {
198
- console.log(
199
- `[raindrop-ai/opencode-plugin] ${opName} attempt ${attempt}/${opts.maxAttempts}`
200
- );
259
+ console.log(`[raindrop-ai/opencode-plugin] ${opName} attempt ${attempt}/${opts.maxAttempts}`);
201
260
  }
202
261
  try {
203
262
  return await operation();
@@ -477,10 +536,12 @@ var TraceShipper = class {
477
536
  const batch = this.queue.splice(0, this.maxBatchSize);
478
537
  const body = buildExportTraceServiceRequest(batch);
479
538
  const url = `${this.baseUrl}traces`;
539
+ const spanNames = batch.map((span) => span.name);
480
540
  if (this.debug) {
481
541
  console.log("[raindrop-ai/opencode-plugin] sending traces batch", {
482
542
  spans: batch.length,
483
- endpoint: url
543
+ endpoint: url,
544
+ spanNames
484
545
  });
485
546
  }
486
547
  const p = postJson(
@@ -494,14 +555,14 @@ var TraceShipper = class {
494
555
  try {
495
556
  await p;
496
557
  if (this.debug) {
497
- console.log(`[raindrop-ai/opencode-plugin] sent ${batch.length} spans`);
558
+ console.log(`[raindrop-ai/opencode-plugin] sent ${batch.length} spans`, {
559
+ spanNames
560
+ });
498
561
  }
499
562
  } catch (err) {
500
563
  if (this.debug) {
501
564
  const msg = err instanceof Error ? err.message : String(err);
502
- console.warn(
503
- `[raindrop-ai/opencode-plugin] failed to send spans (dropping): ${msg}`
504
- );
565
+ console.warn(`[raindrop-ai/opencode-plugin] failed to send spans (dropping): ${msg}`);
505
566
  }
506
567
  }
507
568
  } finally {
@@ -520,328 +581,458 @@ var TraceShipper = class {
520
581
  }
521
582
  };
522
583
 
584
+ // src/tracing-linkage-helpers.ts
585
+ function createSessionParentMapHelpers({
586
+ sessions: sessions2,
587
+ taskContexts: taskContexts2,
588
+ runningTaskCallsBySession: runningTaskCallsBySession2,
589
+ mapChildSessionToParent: mapChildSessionToParent2,
590
+ pendingChildSessionsByCallKey: pendingChildSessionsByCallKey2,
591
+ callKey: callKey2,
592
+ log
593
+ }) {
594
+ function applyChildSessionParentToState(childSessionId) {
595
+ const parentInfo = mapChildSessionToParent2.get(childSessionId);
596
+ const state = sessions2.get(childSessionId);
597
+ if (!parentInfo || !state) return;
598
+ state.parentId = parentInfo.parentId;
599
+ state.parentTaskSpanIds = parentInfo.parentTaskSpanIds;
600
+ state.parentEventContext = parentInfo.eventContext;
601
+ }
602
+ function queuePendingChildByCallKey(key, childSessionId) {
603
+ var _a;
604
+ const pending = (_a = pendingChildSessionsByCallKey2.get(key)) != null ? _a : /* @__PURE__ */ new Set();
605
+ pending.add(childSessionId);
606
+ pendingChildSessionsByCallKey2.set(key, pending);
607
+ }
608
+ function attachChildSessionToParentTask(childSessionId, parentId, parentCallId, source) {
609
+ const key = callKey2(parentId, parentCallId);
610
+ const ctx = taskContexts2.get(key);
611
+ if (!ctx) {
612
+ queuePendingChildByCallKey(key, childSessionId);
613
+ return false;
614
+ }
615
+ const parentInfo = {
616
+ parentId,
617
+ parentTaskSpanIds: ctx.ids,
618
+ eventContext: ctx.eventContext
619
+ };
620
+ mapChildSessionToParent2.set(childSessionId, parentInfo);
621
+ applyChildSessionParentToState(childSessionId);
622
+ return true;
623
+ }
624
+ function resolvePendingChildrenForCall(parentId, parentCallId, source) {
625
+ const key = callKey2(parentId, parentCallId);
626
+ const pending = pendingChildSessionsByCallKey2.get(key);
627
+ if (pending && pending.size > 0) {
628
+ for (const childSessionId of [...pending]) {
629
+ if (attachChildSessionToParentTask(childSessionId, parentId, parentCallId, source)) {
630
+ pending.delete(childSessionId);
631
+ }
632
+ }
633
+ if (pending.size === 0) pendingChildSessionsByCallKey2.delete(key);
634
+ }
635
+ }
636
+ function cleanupSessionParentMap(sessionId) {
637
+ mapChildSessionToParent2.delete(sessionId);
638
+ runningTaskCallsBySession2.delete(sessionId);
639
+ for (const [key, pending] of pendingChildSessionsByCallKey2.entries()) {
640
+ pending.delete(sessionId);
641
+ if (pending.size === 0) pendingChildSessionsByCallKey2.delete(key);
642
+ }
643
+ for (const key of [...taskContexts2.keys()]) {
644
+ if (key.startsWith(`${sessionId}:`)) taskContexts2.delete(key);
645
+ }
646
+ for (const [childSessionId, parentInfo] of [...mapChildSessionToParent2.entries()]) {
647
+ if (parentInfo.parentId === sessionId) {
648
+ mapChildSessionToParent2.delete(childSessionId);
649
+ }
650
+ }
651
+ }
652
+ return {
653
+ applyChildSessionParentToState,
654
+ attachChildSessionToParentTask,
655
+ cleanupSessionParentMap,
656
+ resolvePendingChildrenForCall
657
+ };
658
+ }
659
+
523
660
  // src/tracing.ts
524
661
  var sessions = /* @__PURE__ */ new Map();
662
+ var taskContexts = /* @__PURE__ */ new Map();
663
+ var runningTaskCallsBySession = /* @__PURE__ */ new Map();
664
+ var mapChildSessionToParent = /* @__PURE__ */ new Map();
665
+ var pendingChildSessionsByCallKey = /* @__PURE__ */ new Map();
666
+ function createSessionState(sessionId) {
667
+ return {
668
+ sessionId,
669
+ turnNumber: 0,
670
+ toolCount: 0,
671
+ currentInput: "",
672
+ outputParts: /* @__PURE__ */ new Map(),
673
+ reasoningParts: /* @__PURE__ */ new Map(),
674
+ toolSpanStarts: /* @__PURE__ */ new Map(),
675
+ processedMessages: /* @__PURE__ */ new Set()
676
+ };
677
+ }
678
+ function callKey(sessionId, callId) {
679
+ return `${sessionId}:${callId}`;
680
+ }
525
681
  function getHostname() {
526
- var _a, _b;
682
+ var _a;
527
683
  try {
528
684
  const bunGlobal = globalThis;
529
685
  if ((_a = bunGlobal.Bun) == null ? void 0 : _a.hostname) return bunGlobal.Bun.hostname;
530
686
  return require("os").hostname();
531
687
  } catch (e) {
532
- return (_b = process.env["HOSTNAME"]) != null ? _b : "unknown";
688
+ const h = process.env["HOSTNAME"];
689
+ if (h === void 0 || h === "") throw new Error("HOSTNAME env is not set");
690
+ return h;
533
691
  }
534
692
  }
535
- function getProjectName(worktree) {
536
- var _a;
537
- const parts = worktree.split("/").filter(Boolean);
538
- return (_a = parts[parts.length - 1]) != null ? _a : "unknown";
539
- }
540
- function createHooks(client, config, worktree, directory, eventShipper, traceShipper) {
693
+ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
541
694
  function log(msg, data) {
542
- var _a, _b;
543
- (_b = (_a = client.app.log({
544
- body: {
545
- service: "raindrop-opencode",
546
- level: config.debug ? "info" : "debug",
547
- message: msg,
548
- ...data !== void 0 ? { extra: data } : {}
549
- }
550
- })) == null ? void 0 : _a.catch) == null ? void 0 : _b.call(_a, () => {
551
- });
695
+ if (!config.debug) return;
696
+ const prefix = `[raindrop-ai/opencode-plugin] [info] ${msg}`;
697
+ if (data !== void 0) {
698
+ console.log(prefix, data);
699
+ return;
700
+ }
701
+ console.log(prefix);
552
702
  }
553
703
  const hostname = getHostname();
554
704
  const os = process.platform;
555
- log("Raindrop tracing hooks registered");
705
+ const {
706
+ applyChildSessionParentToState,
707
+ attachChildSessionToParentTask,
708
+ cleanupSessionParentMap,
709
+ resolvePendingChildrenForCall
710
+ } = createSessionParentMapHelpers({
711
+ sessions,
712
+ taskContexts,
713
+ runningTaskCallsBySession,
714
+ mapChildSessionToParent,
715
+ pendingChildSessionsByCallKey,
716
+ callKey,
717
+ log
718
+ });
556
719
  return {
557
720
  // ------------------------------------------------------------------
558
721
  // event — session lifecycle + streaming parts
559
722
  // ------------------------------------------------------------------
560
- event: async ({
561
- event
562
- }) => {
563
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
723
+ event: async ({ event }) => {
724
+ var _a, _b, _c, _d, _e;
564
725
  try {
565
726
  const props = event.properties;
566
727
  const info = props["info"];
567
- const sessionID = props["sessionID"] || (info == null ? void 0 : info["id"]) || props["id"];
728
+ const sessionID = (_b = (_a = props["sessionID"]) != null ? _a : info == null ? void 0 : info["id"]) != null ? _b : props["id"];
729
+ const sessionEventTypes = ["session.created", "session.idle", "session.deleted", "session.error"];
730
+ if (sessionEventTypes.includes(event.type) && (sessionID === void 0 || sessionID === "")) {
731
+ throw new Error(`event ${event.type}: missing sessionID in properties`);
732
+ }
568
733
  if (event.type === "session.created") {
569
- if (!sessionID) {
570
- log("session.created: no session ID, skipping");
571
- return;
734
+ if (info == null || typeof info !== "object")
735
+ throw new Error("session.created: props.info is required");
736
+ const parentID = info["parentID"];
737
+ let state = sessions.get(sessionID);
738
+ if (!state) {
739
+ state = createSessionState(sessionID);
740
+ sessions.set(sessionID, state);
572
741
  }
573
- const userId = (_a = config.userId) != null ? _a : sessionID;
574
- const state = {
575
- sessionId: sessionID,
576
- userId,
577
- turnNumber: 0,
578
- toolCount: 0,
579
- startTime: Date.now(),
580
- currentInput: "",
581
- currentOutput: "",
582
- outputParts: /* @__PURE__ */ new Map(),
583
- reasoningParts: /* @__PURE__ */ new Map(),
584
- toolCalls: /* @__PURE__ */ new Map(),
585
- toolStartTimes: /* @__PURE__ */ new Map(),
586
- toolSpanStarts: /* @__PURE__ */ new Map(),
587
- toolCallMessageIds: /* @__PURE__ */ new Map(),
588
- processedMessages: /* @__PURE__ */ new Set(),
589
- attachments: []
590
- };
591
- state.currentRootSpan = traceShipper.startSpan({
592
- name: `OpenCode: ${getProjectName(worktree)}`,
593
- eventId: sessionID,
594
- attributes: [
595
- attrString("workspace", worktree),
596
- attrString("directory", directory),
597
- attrString("hostname", hostname),
598
- attrString("os", os)
599
- ]
600
- });
601
- sessions.set(sessionID, state);
602
- log("Session created", { sessionID, userId });
742
+ state.parentId = parentID;
743
+ if (state.parentId) mapChildSessionToParent.set(sessionID, { parentId: state.parentId });
603
744
  } else if (event.type === "message.part.updated") {
604
745
  const part = props["part"];
605
- if (!part) return;
606
- const partSessionID = part["sessionID"];
607
- const messageId = part["messageID"];
608
- if (!partSessionID || !messageId) return;
746
+ if (part == null || typeof part !== "object")
747
+ throw new Error("message.part.updated: props.part is required");
748
+ const partObj = part;
749
+ const partSessionID = partObj["sessionID"];
750
+ const messageId = partObj["messageID"];
751
+ if (partSessionID == null || partSessionID === "")
752
+ throw new Error("message.part.updated: part.sessionID is required");
753
+ if (messageId == null || messageId === "")
754
+ throw new Error("message.part.updated: part.messageID is required");
609
755
  const state = sessions.get(partSessionID);
610
756
  if (!state) return;
611
- if (part["type"] === "text" && typeof part["text"] === "string") {
612
- state.outputParts.set(messageId, part["text"]);
613
- } else if (part["type"] === "tool" && messageId) {
614
- const callID = part["callID"];
615
- const tool = part["tool"];
616
- const partState = part["state"];
617
- const input = partState == null ? void 0 : partState["input"];
618
- if (callID && tool && input) {
619
- let calls = state.toolCalls.get(messageId);
620
- if (!calls) {
621
- calls = [];
622
- state.toolCalls.set(messageId, calls);
757
+ if (partObj["type"] === "text" && typeof partObj["text"] === "string") {
758
+ state.outputParts.set(messageId, partObj["text"]);
759
+ } else if (partObj["type"] === "tool" && messageId) {
760
+ const tool = partObj["tool"];
761
+ if (tool === "task") {
762
+ const callID = partObj["callID"];
763
+ const partState = partObj["state"];
764
+ if (callID == null || callID === "")
765
+ throw new Error("message.part.updated: part.callID is required for tool part");
766
+ if (partState == null || typeof partState !== "object")
767
+ throw new Error("message.part.updated: part.state is required for tool part");
768
+ const metadata = partState["metadata"];
769
+ const childSessionId = metadata != null && typeof metadata === "object" ? metadata["sessionId"] : void 0;
770
+ if (childSessionId != null && childSessionId !== "") {
771
+ mapChildSessionToParent.set(childSessionId, {
772
+ parentId: partSessionID
773
+ });
774
+ attachChildSessionToParentTask(
775
+ childSessionId,
776
+ partSessionID,
777
+ callID,
778
+ "part.metadata"
779
+ );
623
780
  }
624
- const existingIdx = calls.findIndex((c) => c.callId === callID);
625
- const entry = { callId: callID, name: tool, args: JSON.stringify(input) };
626
- if (existingIdx >= 0) calls[existingIdx] = entry;
627
- else calls.push(entry);
628
- state.toolCallMessageIds.set(callID, messageId);
629
781
  }
630
- } else if (part["type"] === "reasoning" && typeof part["text"] === "string") {
631
- state.reasoningParts.set(messageId, part["text"]);
632
- } else {
633
- log(`message.part.updated: skipping part type=${String(part["type"])}`);
782
+ } else if (partObj["type"] === "reasoning" && typeof partObj["text"] === "string") {
783
+ state.reasoningParts.set(messageId, partObj["text"]);
634
784
  }
635
785
  } else if (event.type === "message.updated") {
636
786
  const msgInfo = props["info"];
637
- if (!msgInfo) return;
638
- const role = msgInfo["role"];
787
+ if (msgInfo == null || typeof msgInfo !== "object")
788
+ throw new Error("message.updated: props.info is required");
789
+ const info2 = msgInfo;
790
+ const role = info2["role"];
639
791
  if (role !== "assistant") return;
640
- const msgSessionID = msgInfo["sessionID"];
641
- const messageId = msgInfo["id"];
642
- const time = msgInfo["time"];
643
- if (!msgSessionID || !messageId || !(time == null ? void 0 : time["completed"])) return;
792
+ const msgSessionID = info2["sessionID"];
793
+ const messageId = info2["id"];
794
+ const time = info2["time"];
795
+ if (msgSessionID == null || msgSessionID === "")
796
+ throw new Error("message.updated: info.sessionID is required");
797
+ if (messageId == null || messageId === "")
798
+ throw new Error("message.updated: info.id is required");
799
+ if (time == null || typeof time !== "object")
800
+ throw new Error("message.updated: info.time is required");
801
+ const timeObj = time;
802
+ if (timeObj["completed"] == null) return;
644
803
  const state = sessions.get(msgSessionID);
645
- if (!state || !state.currentEventId || !state.currentTurnSpan) return;
804
+ if (!state || !state.currentEventId || !state.currentRootSpan) return;
646
805
  if (state.processedMessages.has(messageId)) return;
647
806
  state.processedMessages.add(messageId);
648
- const tokens = msgInfo["tokens"];
649
- const inputTokens = (tokens == null ? void 0 : tokens["input"]) || 0;
650
- const outputTokens = (tokens == null ? void 0 : tokens["output"]) || 0;
651
- const reasoningTokens = (tokens == null ? void 0 : tokens["reasoning"]) || 0;
652
- const providerID = msgInfo["providerID"] || "unknown";
653
- const modelID = msgInfo["modelID"] || "unknown";
807
+ const tokens = info2["tokens"];
808
+ if (tokens == null || typeof tokens !== "object")
809
+ throw new Error("message.updated: info.tokens is required");
810
+ const tokensObj = tokens;
811
+ const inputTokens = tokensObj["input"];
812
+ const outputTokens = tokensObj["output"];
813
+ const reasoningTokens = tokensObj["reasoning"];
814
+ if (typeof inputTokens !== "number" || typeof outputTokens !== "number")
815
+ throw new Error("message.updated: tokens.input and tokens.output must be numbers");
816
+ const providerID = info2["providerID"];
817
+ const modelID = info2["modelID"];
818
+ if (typeof providerID !== "string" || providerID === "" || typeof modelID !== "string" || modelID === "")
819
+ throw new Error(
820
+ "message.updated: providerID and modelID are required non-empty strings"
821
+ );
654
822
  const modelName = `${providerID}/${modelID}`;
655
- state.model = modelName;
656
- const outputText = (_b = state.outputParts.get(messageId)) != null ? _b : "";
657
- state.currentOutput = outputText;
658
- const llmAttrs = [
659
- attrString("gen_ai.system", providerID),
660
- attrString("gen_ai.request.model", modelID),
661
- attrInt("gen_ai.usage.input_tokens", inputTokens),
662
- attrInt("gen_ai.usage.output_tokens", outputTokens)
663
- ];
664
- if (reasoningTokens > 0) {
665
- llmAttrs.push(attrInt("gen_ai.usage.reasoning_tokens", reasoningTokens));
666
- }
667
- const llmSpan = traceShipper.startSpan({
668
- name: modelName,
669
- parent: state.currentTurnSpan.ids,
670
- eventId: state.currentEventId,
671
- attributes: llmAttrs
672
- });
673
- if (typeof time["created"] === "number") {
674
- llmSpan.startTimeUnixNano = String(time["created"] * 1e6);
823
+ const finishReason = info2["finish"];
824
+ const outputText = state.outputParts.get(messageId);
825
+ const reasoningText = state.reasoningParts.get(messageId);
826
+ const isToolCallsCompletion = finishReason === "tool-calls";
827
+ const hasIntermediateReasoning = reasoningText !== void 0 && reasoningText.trim().length > 0;
828
+ if (outputText === void 0 && !(isToolCallsCompletion && hasIntermediateReasoning))
829
+ return;
830
+ if (isToolCallsCompletion && !hasIntermediateReasoning) {
831
+ await traceShipper.flush();
832
+ return;
675
833
  }
676
- const msgError = msgInfo["error"];
677
- const errorForSpan = msgError ? `${(_e = (_d = (_c = msgError.data) == null ? void 0 : _c.message) != null ? _d : msgError.name) != null ? _e : "Error"}
834
+ const msgError = info2["error"];
835
+ const errorForSpan = msgError ? (() => {
836
+ var _a2, _b2;
837
+ const msg = (_b2 = (_a2 = msgError.data) == null ? void 0 : _a2.message) != null ? _b2 : msgError.name;
838
+ const name = msgError.name;
839
+ if (msg == null && name == null)
840
+ throw new Error("message.updated: error object must have name or data.message");
841
+ return `${msg != null ? msg : name}
678
842
 
679
- type: ${(_f = msgError.name) != null ? _f : "UnknownError"}` : void 0;
680
- traceShipper.endSpan(llmSpan, { error: errorForSpan });
681
- await eventShipper.finish(state.currentEventId, {
682
- userId: state.userId,
683
- model: modelName,
684
- output: outputText || void 0,
685
- properties: {
686
- turn_count: state.turnNumber,
687
- tool_count: state.toolCount
843
+ type: ${name != null ? name : "UnknownError"}`;
844
+ })() : void 0;
845
+ if (isToolCallsCompletion) {
846
+ if (reasoningText === void 0)
847
+ throw new Error("message.updated: reasoningParts missing for tool-call continuation");
848
+ const llmAttrs = [
849
+ attrString("ai.operationId", "generateText"),
850
+ attrString("ai.response.text", reasoningText),
851
+ attrString("gen_ai.system", providerID),
852
+ attrString("gen_ai.request.model", modelID),
853
+ attrInt("gen_ai.usage.input_tokens", inputTokens),
854
+ attrInt("gen_ai.usage.output_tokens", outputTokens)
855
+ ];
856
+ if (reasoningTokens != null && typeof reasoningTokens === "number" && reasoningTokens > 0) {
857
+ llmAttrs.push(attrInt("gen_ai.usage.reasoning_tokens", reasoningTokens));
688
858
  }
689
- });
859
+ const llmParent = state.parentId && state.parentTaskSpanIds ? state.parentTaskSpanIds : state.currentRootSpan.ids;
860
+ const llmSpan = traceShipper.startSpan({
861
+ name: modelName,
862
+ parent: llmParent,
863
+ eventId: state.currentEventId,
864
+ attributes: llmAttrs
865
+ });
866
+ if (typeof timeObj["created"] === "number") {
867
+ llmSpan.startTimeUnixNano = String(Math.floor(timeObj["created"])) + "000000";
868
+ }
869
+ traceShipper.endSpan(llmSpan, { error: errorForSpan });
870
+ await traceShipper.flush();
871
+ return;
872
+ }
873
+ if (outputText === void 0) return;
874
+ const rootSpan = state.currentRootSpan;
875
+ if (state.parentId) {
876
+ const llmAttrs = [
877
+ attrString("ai.operationId", "generateText"),
878
+ attrString("ai.prompt", state.currentInput),
879
+ attrString("ai.response.text", outputText),
880
+ attrString("gen_ai.system", providerID),
881
+ attrString("gen_ai.request.model", modelID),
882
+ attrInt("gen_ai.usage.input_tokens", inputTokens),
883
+ attrInt("gen_ai.usage.output_tokens", outputTokens)
884
+ ];
885
+ if (reasoningTokens != null && typeof reasoningTokens === "number" && reasoningTokens > 0) {
886
+ llmAttrs.push(attrInt("gen_ai.usage.reasoning_tokens", reasoningTokens));
887
+ }
888
+ if (!state.parentTaskSpanIds) {
889
+ traceShipper.endSpan(rootSpan, {
890
+ error: "Missing strict parent task for child session"
891
+ });
892
+ state.currentRootSpan = void 0;
893
+ state.currentEventId = void 0;
894
+ await traceShipper.flush();
895
+ return;
896
+ }
897
+ const finalLlmSpan = traceShipper.startSpan({
898
+ name: modelName,
899
+ parent: state.parentTaskSpanIds,
900
+ eventId: state.currentEventId,
901
+ attributes: llmAttrs
902
+ });
903
+ if (typeof timeObj["created"] === "number") {
904
+ finalLlmSpan.startTimeUnixNano = String(Math.floor(timeObj["created"])) + "000000";
905
+ }
906
+ traceShipper.endSpan(finalLlmSpan, { error: errorForSpan });
907
+ traceShipper.endSpan(rootSpan, {
908
+ attributes: [
909
+ attrString("is_subagent", "true"),
910
+ attrString("parent_session_id", state.parentId)
911
+ ],
912
+ error: errorForSpan
913
+ });
914
+ } else {
915
+ rootSpan.name = modelName;
916
+ const rootAttrs = [
917
+ attrString("ai.operationId", "generateText"),
918
+ attrString("ai.prompt", state.currentInput),
919
+ attrString("ai.response.text", outputText),
920
+ attrString("gen_ai.system", providerID),
921
+ attrString("gen_ai.request.model", modelID),
922
+ attrInt("gen_ai.usage.input_tokens", inputTokens),
923
+ attrInt("gen_ai.usage.output_tokens", outputTokens)
924
+ ];
925
+ if (reasoningTokens != null && typeof reasoningTokens === "number" && reasoningTokens > 0) {
926
+ rootAttrs.push(attrInt("gen_ai.usage.reasoning_tokens", reasoningTokens));
927
+ }
928
+ traceShipper.endSpan(rootSpan, {
929
+ attributes: rootAttrs,
930
+ error: errorForSpan
931
+ });
932
+ }
933
+ state.currentRootSpan = void 0;
934
+ const hasAssistantResponse = outputText.trim().length > 0;
935
+ if (!hasAssistantResponse) {
936
+ await traceShipper.flush();
937
+ state.currentEventId = void 0;
938
+ return;
939
+ }
940
+ if (!state.parentId) {
941
+ await eventShipper.finish(state.currentEventId, {
942
+ userId: state.sessionId,
943
+ model: modelName,
944
+ output: outputText,
945
+ properties: {
946
+ turn_count: state.turnNumber,
947
+ tool_count: state.toolCount
948
+ }
949
+ });
950
+ }
690
951
  await traceShipper.flush();
691
- log("LLM span created, turn/root close on session.idle", {
692
- messageId,
693
- modelName,
694
- inputTokens,
695
- outputTokens,
696
- reasoningTokens
697
- });
952
+ state.currentEventId = void 0;
698
953
  } else if (event.type === "session.idle") {
699
954
  if (!sessionID) return;
700
955
  const state = sessions.get(String(sessionID));
701
956
  if (!state) return;
702
- if (state.currentTurnSpan) {
703
- traceShipper.endSpan(state.currentTurnSpan);
704
- state.currentTurnSpan = void 0;
705
- }
706
957
  if (state.currentRootSpan) {
707
958
  traceShipper.endSpan(state.currentRootSpan);
708
959
  state.currentRootSpan = void 0;
709
960
  }
710
961
  if (state.currentEventId) {
711
- await eventShipper.finish(state.currentEventId, {
712
- userId: state.userId,
713
- output: state.currentOutput || void 0,
714
- properties: {
715
- turn_count: state.turnNumber,
716
- tool_count: state.toolCount
717
- }
718
- });
719
962
  state.currentEventId = void 0;
720
963
  }
721
- await traceShipper.flush();
722
- log("Session idle \u2014 turn and root closed", { sessionID });
964
+ await Promise.all([eventShipper.flush(), traceShipper.flush()]);
965
+ if (state.parentId) {
966
+ cleanupSessionParentMap(String(sessionID));
967
+ sessions.delete(String(sessionID));
968
+ } else {
969
+ }
723
970
  } else if (event.type === "session.deleted") {
724
971
  if (!sessionID) return;
725
972
  const state = sessions.get(String(sessionID));
726
973
  if (!state) return;
727
- if (state.currentTurnSpan) {
728
- traceShipper.endSpan(state.currentTurnSpan);
729
- state.currentTurnSpan = void 0;
730
- }
731
974
  if (state.currentRootSpan) {
732
975
  traceShipper.endSpan(state.currentRootSpan);
733
976
  state.currentRootSpan = void 0;
734
977
  }
735
978
  if (state.currentEventId) {
736
- await eventShipper.finish(state.currentEventId, {
737
- userId: state.userId,
738
- output: state.currentOutput || void 0,
739
- model: state.model,
740
- properties: {
741
- turn_count: state.turnNumber,
742
- tool_count: state.toolCount
743
- }
744
- });
745
979
  state.currentEventId = void 0;
746
980
  }
981
+ cleanupSessionParentMap(String(sessionID));
747
982
  await Promise.all([eventShipper.flush(), traceShipper.flush()]);
748
983
  sessions.delete(String(sessionID));
749
- log("Session deleted \u2014 flushed", { sessionID });
750
984
  } else if (event.type === "session.error") {
751
- const errorSessionID = props["sessionID"] || sessionID;
752
- if (!errorSessionID) return;
985
+ const errorSessionID = (_c = props["sessionID"]) != null ? _c : sessionID;
986
+ if (errorSessionID === void 0 || errorSessionID === "")
987
+ throw new Error("session.error: missing sessionID");
753
988
  const state = sessions.get(String(errorSessionID));
754
989
  if (!state) return;
755
990
  const errorObj = props["error"];
756
- const errorName = (_g = errorObj == null ? void 0 : errorObj.name) != null ? _g : "UnknownError";
757
- const errorMessage = (_i = (_h = errorObj == null ? void 0 : errorObj.data) == null ? void 0 : _h.message) != null ? _i : errorName;
758
- const errorStr = `${errorMessage}
991
+ if (errorObj == null) throw new Error("session.error: missing error object");
992
+ const errorName = errorObj.name;
993
+ const errorMessage = (_e = (_d = errorObj.data) == null ? void 0 : _d.message) != null ? _e : errorObj.name;
994
+ if (errorName == null && errorMessage == null)
995
+ throw new Error("session.error: error object must have name or data.message");
996
+ const errorStr = `${errorMessage != null ? errorMessage : errorName}
759
997
 
760
- type: ${errorName}`;
761
- if (state.currentTurnSpan) {
762
- traceShipper.endSpan(state.currentTurnSpan, { error: errorStr });
763
- state.currentTurnSpan = void 0;
764
- }
998
+ type: ${errorName != null ? errorName : "UnknownError"}`;
765
999
  if (state.currentRootSpan) {
766
1000
  traceShipper.endSpan(state.currentRootSpan, { error: errorStr });
767
1001
  state.currentRootSpan = void 0;
768
1002
  }
769
1003
  if (state.currentEventId) {
770
- await eventShipper.finish(state.currentEventId, {
771
- userId: state.userId,
772
- properties: {
773
- error: errorStr,
774
- turn_count: state.turnNumber,
775
- tool_count: state.toolCount
776
- }
777
- });
778
1004
  state.currentEventId = void 0;
779
1005
  }
1006
+ cleanupSessionParentMap(String(errorSessionID));
780
1007
  await Promise.all([eventShipper.flush(), traceShipper.flush()]);
781
1008
  sessions.delete(String(errorSessionID));
782
- log("Session error handled", { errorSessionID, errorName });
783
- } else {
784
- log(`unhandled event: ${event.type}`);
785
1009
  }
786
1010
  } catch (err) {
787
- (_k = (_j = client.app.log({
788
- body: {
789
- service: "raindrop-opencode",
790
- level: "error",
791
- message: `Error in event hook: ${err instanceof Error ? err.message : String(err)}`
792
- }
793
- })) == null ? void 0 : _j.catch) == null ? void 0 : _k.call(_j, () => {
794
- });
1011
+ console.log(
1012
+ `[raindrop-ai/opencode-plugin] [error] Error in event hook: ${err instanceof Error ? err.message : String(err)}`
1013
+ );
795
1014
  }
796
1015
  },
797
1016
  // ------------------------------------------------------------------
798
1017
  // chat.message — user sent a message; start a new turn with a fresh event ID
799
1018
  // ------------------------------------------------------------------
800
1019
  "chat.message": async (messageInput, output) => {
801
- var _a, _b;
802
1020
  try {
803
1021
  const { sessionID } = messageInput;
804
1022
  let state = sessions.get(sessionID);
805
1023
  if (!state) {
806
- state = {
807
- sessionId: sessionID,
808
- userId: (_a = config.userId) != null ? _a : sessionID,
809
- turnNumber: 0,
810
- toolCount: 0,
811
- startTime: Date.now(),
812
- currentInput: "",
813
- currentOutput: "",
814
- outputParts: /* @__PURE__ */ new Map(),
815
- reasoningParts: /* @__PURE__ */ new Map(),
816
- toolCalls: /* @__PURE__ */ new Map(),
817
- toolStartTimes: /* @__PURE__ */ new Map(),
818
- toolSpanStarts: /* @__PURE__ */ new Map(),
819
- toolCallMessageIds: /* @__PURE__ */ new Map(),
820
- processedMessages: /* @__PURE__ */ new Set(),
821
- attachments: []
822
- };
1024
+ state = createSessionState(sessionID);
823
1025
  sessions.set(sessionID, state);
824
1026
  }
825
- if (state.currentTurnSpan) {
826
- traceShipper.endSpan(state.currentTurnSpan);
827
- state.currentTurnSpan = void 0;
828
- }
829
- if (!state.currentRootSpan) {
830
- state.currentRootSpan = traceShipper.startSpan({
831
- name: `OpenCode: ${getProjectName(worktree)}`,
832
- eventId: sessionID,
833
- attributes: [
834
- attrString("workspace", worktree),
835
- attrString("directory", directory),
836
- attrString("hostname", hostname),
837
- attrString("os", os)
838
- ]
839
- });
1027
+ applyChildSessionParentToState(sessionID);
1028
+ if (state.currentRootSpan) {
1029
+ traceShipper.endSpan(state.currentRootSpan);
1030
+ state.currentRootSpan = void 0;
840
1031
  }
841
1032
  state.turnNumber++;
842
- state.currentOutput = "";
843
- state.currentEventId = generateId();
844
- const parts = (_b = output == null ? void 0 : output.parts) != null ? _b : [];
1033
+ if (output == null) throw new Error("chat.message: output is required");
1034
+ const parts = output.parts;
1035
+ if (parts == null) throw new Error("chat.message: output.parts is required");
845
1036
  const textParts = [];
846
1037
  const newAttachments = [];
847
1038
  for (const part of parts) {
@@ -852,26 +1043,52 @@ type: ${errorName}`;
852
1043
  const filename = part["filename"];
853
1044
  const url = part["url"];
854
1045
  if (url && (mimeType == null ? void 0 : mimeType.startsWith("image/"))) {
1046
+ if (filename === void 0 || filename === "")
1047
+ throw new Error("chat.message: file part must have filename");
855
1048
  newAttachments.push({ type: "image", role: "input", name: filename, value: url });
856
1049
  }
857
1050
  }
858
1051
  }
859
1052
  const userText = textParts.join("\n");
860
1053
  state.currentInput = userText;
861
- if (newAttachments.length > 0) state.attachments.push(...newAttachments);
862
- state.currentTurnSpan = traceShipper.startSpan({
863
- name: `Turn ${state.turnNumber}`,
864
- parent: state.currentRootSpan.ids,
1054
+ if (state.parentId) {
1055
+ if (!state.parentTaskSpanIds || !state.parentEventContext) {
1056
+ return;
1057
+ }
1058
+ state.currentEventId = state.parentEventContext.eventId;
1059
+ state.currentRootSpan = traceShipper.startSpan({
1060
+ name: "Subagent",
1061
+ parent: state.parentTaskSpanIds,
1062
+ eventId: state.currentEventId,
1063
+ attributes: [
1064
+ attrString("workspace", worktree),
1065
+ attrString("directory", directory),
1066
+ attrString("hostname", hostname),
1067
+ attrString("os", os),
1068
+ attrString("is_subagent", "true"),
1069
+ attrString("parent_session_id", state.parentId)
1070
+ ]
1071
+ });
1072
+ return;
1073
+ }
1074
+ state.currentEventId = generateId();
1075
+ state.currentRootSpan = traceShipper.startSpan({
1076
+ name: "ai.event",
865
1077
  eventId: state.currentEventId,
866
- attributes: [attrInt("turn.number", state.turnNumber)]
1078
+ attributes: [
1079
+ attrString("workspace", worktree),
1080
+ attrString("directory", directory),
1081
+ attrString("hostname", hostname),
1082
+ attrString("os", os)
1083
+ ]
867
1084
  });
868
1085
  await eventShipper.patch(state.currentEventId, {
869
1086
  isPending: true,
870
- userId: state.userId,
1087
+ userId: state.sessionId,
871
1088
  convoId: state.sessionId,
872
1089
  eventName: config.eventName,
873
- input: userText || void 0,
874
- attachments: newAttachments.length > 0 ? newAttachments : void 0,
1090
+ input: userText,
1091
+ ...newAttachments.length > 0 ? { attachments: newAttachments } : {},
875
1092
  properties: {
876
1093
  workspace: worktree,
877
1094
  directory,
@@ -881,74 +1098,143 @@ type: ${errorName}`;
881
1098
  tool_count: state.toolCount
882
1099
  }
883
1100
  });
884
- log("Chat message \u2014 new turn started", {
885
- sessionID,
886
- eventId: state.currentEventId,
887
- turnNumber: state.turnNumber,
888
- inputLength: userText.length,
889
- attachments: newAttachments.length
890
- });
891
1101
  } catch (err) {
892
- log("Error in chat.message hook", { error: String(err) });
893
1102
  }
894
1103
  },
895
1104
  // ------------------------------------------------------------------
896
1105
  // tool.execute.before — record start time only (span created atomically in after)
1106
+ // For child sessions, tool spans are parented directly to the parent task span.
897
1107
  // ------------------------------------------------------------------
898
1108
  "tool.execute.before": async (toolInput, _output) => {
1109
+ var _a;
899
1110
  try {
900
1111
  const { tool, sessionID, callID } = toolInput;
901
1112
  const state = sessions.get(sessionID);
902
- if (!state || !state.currentTurnSpan || !state.currentEventId) return;
903
- state.toolStartTimes.set(callID, Date.now());
904
- state.toolSpanStarts.set(callID, {
905
- startTimeUnixNano: nowUnixNanoString(),
906
- parent: state.currentTurnSpan.ids,
907
- eventId: state.currentEventId,
908
- name: tool
909
- });
910
- log("Tool execute before", { tool, sessionID, callID });
1113
+ if (!state || !state.currentEventId) return;
1114
+ const spanParent = state.parentId && state.parentTaskSpanIds ? state.parentTaskSpanIds : (_a = state.currentRootSpan) == null ? void 0 : _a.ids;
1115
+ if (!spanParent) return;
1116
+ const startTimeUnixNano = nowUnixNanoString();
1117
+ if (tool === "task") {
1118
+ const liveTaskSpan = traceShipper.startSpan({
1119
+ name: "ai.toolCall",
1120
+ parent: spanParent,
1121
+ eventId: state.currentEventId,
1122
+ attributes: [
1123
+ attrString("ai.operationId", "ai.toolCall"),
1124
+ attrString("ai.toolCall.name", tool),
1125
+ attrString("ai.toolCall.id", callID)
1126
+ ]
1127
+ });
1128
+ liveTaskSpan.startTimeUnixNano = startTimeUnixNano;
1129
+ const ctx = {
1130
+ ids: liveTaskSpan.ids,
1131
+ eventContext: {
1132
+ eventId: state.currentEventId,
1133
+ userId: state.sessionId,
1134
+ convoId: state.sessionId
1135
+ }
1136
+ };
1137
+ taskContexts.set(callKey(sessionID, callID), ctx);
1138
+ let running = runningTaskCallsBySession.get(sessionID);
1139
+ if (running == null) {
1140
+ running = /* @__PURE__ */ new Set();
1141
+ runningTaskCallsBySession.set(sessionID, running);
1142
+ }
1143
+ running.add(callID);
1144
+ resolvePendingChildrenForCall(sessionID, callID, "tool.execute.before");
1145
+ state.toolSpanStarts.set(callID, {
1146
+ startTimeUnixNano,
1147
+ parent: spanParent,
1148
+ eventId: state.currentEventId,
1149
+ name: tool,
1150
+ liveSpan: liveTaskSpan
1151
+ });
1152
+ } else {
1153
+ state.toolSpanStarts.set(callID, {
1154
+ startTimeUnixNano,
1155
+ parent: spanParent,
1156
+ eventId: state.currentEventId,
1157
+ name: tool
1158
+ });
1159
+ }
911
1160
  } catch (err) {
912
- log("Error in tool.execute.before hook", { error: String(err) });
1161
+ console.log(
1162
+ `[raindrop-ai/opencode-plugin] [error] Error in tool.execute.before hook: ${err instanceof Error ? err.message : String(err)}`
1163
+ );
913
1164
  }
914
1165
  },
915
1166
  // ------------------------------------------------------------------
916
1167
  // tool.execute.after — create tool span atomically with both start and end times
917
1168
  // ------------------------------------------------------------------
918
1169
  "tool.execute.after": async (toolInput, result) => {
919
- var _a;
920
1170
  try {
921
- const { tool, sessionID, callID } = toolInput;
1171
+ const { tool, sessionID, callID, args } = toolInput;
922
1172
  const state = sessions.get(sessionID);
923
1173
  if (!state || !state.currentEventId) return;
924
1174
  state.toolCount++;
925
- state.toolStartTimes.delete(callID);
926
- state.toolCallMessageIds.delete(callID);
1175
+ if (args === void 0 || args === null)
1176
+ throw new Error("tool.execute.after: args is required");
1177
+ const toolCallArgs = typeof args === "string" ? args : JSON.stringify(args);
927
1178
  const startInfo = state.toolSpanStarts.get(callID);
928
1179
  state.toolSpanStarts.delete(callID);
1180
+ const resultMetadata = result.metadata;
1181
+ const childSessionId = resultMetadata == null ? void 0 : resultMetadata["sessionId"];
1182
+ if (tool === "task" && childSessionId) {
1183
+ mapChildSessionToParent.set(childSessionId, { parentId: sessionID });
1184
+ attachChildSessionToParentTask(childSessionId, sessionID, callID, "tool.execute.after");
1185
+ }
929
1186
  if (startInfo) {
930
1187
  const endTimeUnixNano = nowUnixNanoString();
931
- const outputPreview = typeof result.output === "string" ? result.output.slice(0, 200) : JSON.stringify((_a = result.output) != null ? _a : "").slice(0, 200);
932
- traceShipper.createSpan({
933
- name: startInfo.name,
934
- parent: startInfo.parent,
935
- eventId: startInfo.eventId,
936
- startTimeUnixNano: startInfo.startTimeUnixNano,
937
- endTimeUnixNano,
938
- attributes: [
939
- attrString("tool.name", tool),
940
- attrString("tool.title", result.title),
941
- attrString("tool.output_preview", outputPreview)
942
- ]
1188
+ if (!("output" in result) || result.output === void 0)
1189
+ throw new Error("tool.execute.after: result.output is required");
1190
+ let toolResult;
1191
+ try {
1192
+ toolResult = JSON.stringify(result.output);
1193
+ } catch (err) {
1194
+ const msg = err instanceof Error ? err.message : String(err);
1195
+ throw new Error(`tool.execute.after: result.output must be JSON-serializable: ${msg}`);
1196
+ }
1197
+ if (startInfo.liveSpan) {
1198
+ traceShipper.endSpan(startInfo.liveSpan, {
1199
+ attributes: [
1200
+ attrString("ai.toolCall.args", toolCallArgs),
1201
+ attrString("ai.toolCall.result", toolResult)
1202
+ ]
1203
+ });
1204
+ } else {
1205
+ traceShipper.createSpan({
1206
+ name: "ai.toolCall",
1207
+ parent: startInfo.parent,
1208
+ eventId: startInfo.eventId,
1209
+ startTimeUnixNano: startInfo.startTimeUnixNano,
1210
+ endTimeUnixNano,
1211
+ attributes: [
1212
+ attrString("ai.operationId", "ai.toolCall"),
1213
+ attrString("ai.toolCall.name", tool),
1214
+ attrString("ai.toolCall.id", callID),
1215
+ attrString("ai.toolCall.args", toolCallArgs),
1216
+ attrString("ai.toolCall.result", toolResult)
1217
+ ]
1218
+ });
1219
+ }
1220
+ }
1221
+ if (tool === "task") {
1222
+ const running = runningTaskCallsBySession.get(sessionID);
1223
+ if (running) {
1224
+ running.delete(callID);
1225
+ if (running.size === 0) runningTaskCallsBySession.delete(sessionID);
1226
+ }
1227
+ }
1228
+ if (!state.parentId) {
1229
+ await eventShipper.patch(state.currentEventId, {
1230
+ userId: state.sessionId,
1231
+ properties: { tool_count: state.toolCount }
943
1232
  });
944
1233
  }
945
- await eventShipper.patch(state.currentEventId, {
946
- userId: state.userId,
947
- properties: { tool_count: state.toolCount }
948
- });
949
- log("Tool execute after", { tool, sessionID, callID });
950
1234
  } catch (err) {
951
- log("Error in tool.execute.after hook", { error: String(err) });
1235
+ console.log(
1236
+ `[raindrop-ai/opencode-plugin] [error] Error in tool.execute.after hook: ${err instanceof Error ? err.message : String(err)}`
1237
+ );
952
1238
  }
953
1239
  }
954
1240
  };
@@ -957,18 +1243,9 @@ type: ${errorName}`;
957
1243
  // src/index.ts
958
1244
  async function plugin(input) {
959
1245
  var _a;
960
- const { client } = input;
961
1246
  const config = loadConfig(input.directory);
962
1247
  function appLog(level, message) {
963
- var _a2, _b;
964
- (_b = (_a2 = client.app.log({
965
- body: {
966
- service: "raindrop-opencode",
967
- level,
968
- message
969
- }
970
- })) == null ? void 0 : _a2.catch) == null ? void 0 : _b.call(_a2, () => {
971
- });
1248
+ console.log(`[raindrop-ai/opencode-plugin] [${level}] ${message}`);
972
1249
  }
973
1250
  if (!config.writeKey) {
974
1251
  appLog(
@@ -992,7 +1269,6 @@ async function plugin(input) {
992
1269
  });
993
1270
  const worktree = (_a = input.worktree) != null ? _a : input.directory;
994
1271
  const hooks = createHooks(
995
- client,
996
1272
  config,
997
1273
  worktree,
998
1274
  input.directory,