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