@raindrop-ai/opencode-plugin 0.0.2 → 0.0.3
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.
- package/dist/index.cjs +507 -296
- package/dist/index.js +507 -296
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -96,8 +96,6 @@ function base64Encode(bytes) {
|
|
|
96
96
|
var SERVICE_NAME = "raindrop.opencode-plugin";
|
|
97
97
|
var PLUGIN_VERSION = "0.1.0";
|
|
98
98
|
var SpanStatusCode = {
|
|
99
|
-
UNSET: 0,
|
|
100
|
-
OK: 1,
|
|
101
99
|
ERROR: 2
|
|
102
100
|
};
|
|
103
101
|
function generateId() {
|
|
@@ -125,7 +123,11 @@ function attrInt(key, value) {
|
|
|
125
123
|
return { key, value: { intValue: String(Math.trunc(value)) } };
|
|
126
124
|
}
|
|
127
125
|
function buildOtlpSpan(args) {
|
|
128
|
-
const attrs =
|
|
126
|
+
const attrs = [
|
|
127
|
+
attrString("span.id", args.ids.spanIdB64),
|
|
128
|
+
attrString("span.parent.id", args.ids.parentSpanIdB64),
|
|
129
|
+
...args.attributes
|
|
130
|
+
].filter((x) => x !== void 0);
|
|
129
131
|
const span = {
|
|
130
132
|
traceId: args.ids.traceIdB64,
|
|
131
133
|
spanId: args.ids.spanIdB64,
|
|
@@ -195,9 +197,7 @@ async function withRetry(operation, opName, opts) {
|
|
|
195
197
|
}
|
|
196
198
|
if (delay > 0) await wait(delay);
|
|
197
199
|
} else if (opts.debug) {
|
|
198
|
-
console.log(
|
|
199
|
-
`[raindrop-ai/opencode-plugin] ${opName} attempt ${attempt}/${opts.maxAttempts}`
|
|
200
|
-
);
|
|
200
|
+
console.log(`[raindrop-ai/opencode-plugin] ${opName} attempt ${attempt}/${opts.maxAttempts}`);
|
|
201
201
|
}
|
|
202
202
|
try {
|
|
203
203
|
return await operation();
|
|
@@ -477,10 +477,12 @@ var TraceShipper = class {
|
|
|
477
477
|
const batch = this.queue.splice(0, this.maxBatchSize);
|
|
478
478
|
const body = buildExportTraceServiceRequest(batch);
|
|
479
479
|
const url = `${this.baseUrl}traces`;
|
|
480
|
+
const spanNames = batch.map((span) => span.name);
|
|
480
481
|
if (this.debug) {
|
|
481
482
|
console.log("[raindrop-ai/opencode-plugin] sending traces batch", {
|
|
482
483
|
spans: batch.length,
|
|
483
|
-
endpoint: url
|
|
484
|
+
endpoint: url,
|
|
485
|
+
spanNames
|
|
484
486
|
});
|
|
485
487
|
}
|
|
486
488
|
const p = postJson(
|
|
@@ -494,14 +496,14 @@ var TraceShipper = class {
|
|
|
494
496
|
try {
|
|
495
497
|
await p;
|
|
496
498
|
if (this.debug) {
|
|
497
|
-
console.log(`[raindrop-ai/opencode-plugin] sent ${batch.length} spans
|
|
499
|
+
console.log(`[raindrop-ai/opencode-plugin] sent ${batch.length} spans`, {
|
|
500
|
+
spanNames
|
|
501
|
+
});
|
|
498
502
|
}
|
|
499
503
|
} catch (err) {
|
|
500
504
|
if (this.debug) {
|
|
501
505
|
const msg = err instanceof Error ? err.message : String(err);
|
|
502
|
-
console.warn(
|
|
503
|
-
`[raindrop-ai/opencode-plugin] failed to send spans (dropping): ${msg}`
|
|
504
|
-
);
|
|
506
|
+
console.warn(`[raindrop-ai/opencode-plugin] failed to send spans (dropping): ${msg}`);
|
|
505
507
|
}
|
|
506
508
|
}
|
|
507
509
|
} finally {
|
|
@@ -520,328 +522,458 @@ var TraceShipper = class {
|
|
|
520
522
|
}
|
|
521
523
|
};
|
|
522
524
|
|
|
525
|
+
// src/tracing-linkage-helpers.ts
|
|
526
|
+
function createSessionParentMapHelpers({
|
|
527
|
+
sessions: sessions2,
|
|
528
|
+
taskContexts: taskContexts2,
|
|
529
|
+
runningTaskCallsBySession: runningTaskCallsBySession2,
|
|
530
|
+
mapChildSessionToParent: mapChildSessionToParent2,
|
|
531
|
+
pendingChildSessionsByCallKey: pendingChildSessionsByCallKey2,
|
|
532
|
+
callKey: callKey2,
|
|
533
|
+
log
|
|
534
|
+
}) {
|
|
535
|
+
function applyChildSessionParentToState(childSessionId) {
|
|
536
|
+
const parentInfo = mapChildSessionToParent2.get(childSessionId);
|
|
537
|
+
const state = sessions2.get(childSessionId);
|
|
538
|
+
if (!parentInfo || !state) return;
|
|
539
|
+
state.parentId = parentInfo.parentId;
|
|
540
|
+
state.parentTaskSpanIds = parentInfo.parentTaskSpanIds;
|
|
541
|
+
state.parentEventContext = parentInfo.eventContext;
|
|
542
|
+
}
|
|
543
|
+
function queuePendingChildByCallKey(key, childSessionId) {
|
|
544
|
+
var _a;
|
|
545
|
+
const pending = (_a = pendingChildSessionsByCallKey2.get(key)) != null ? _a : /* @__PURE__ */ new Set();
|
|
546
|
+
pending.add(childSessionId);
|
|
547
|
+
pendingChildSessionsByCallKey2.set(key, pending);
|
|
548
|
+
}
|
|
549
|
+
function attachChildSessionToParentTask(childSessionId, parentId, parentCallId, source) {
|
|
550
|
+
const key = callKey2(parentId, parentCallId);
|
|
551
|
+
const ctx = taskContexts2.get(key);
|
|
552
|
+
if (!ctx) {
|
|
553
|
+
queuePendingChildByCallKey(key, childSessionId);
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
const parentInfo = {
|
|
557
|
+
parentId,
|
|
558
|
+
parentTaskSpanIds: ctx.ids,
|
|
559
|
+
eventContext: ctx.eventContext
|
|
560
|
+
};
|
|
561
|
+
mapChildSessionToParent2.set(childSessionId, parentInfo);
|
|
562
|
+
applyChildSessionParentToState(childSessionId);
|
|
563
|
+
return true;
|
|
564
|
+
}
|
|
565
|
+
function resolvePendingChildrenForCall(parentId, parentCallId, source) {
|
|
566
|
+
const key = callKey2(parentId, parentCallId);
|
|
567
|
+
const pending = pendingChildSessionsByCallKey2.get(key);
|
|
568
|
+
if (pending && pending.size > 0) {
|
|
569
|
+
for (const childSessionId of [...pending]) {
|
|
570
|
+
if (attachChildSessionToParentTask(childSessionId, parentId, parentCallId, source)) {
|
|
571
|
+
pending.delete(childSessionId);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
if (pending.size === 0) pendingChildSessionsByCallKey2.delete(key);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
function cleanupSessionParentMap(sessionId) {
|
|
578
|
+
mapChildSessionToParent2.delete(sessionId);
|
|
579
|
+
runningTaskCallsBySession2.delete(sessionId);
|
|
580
|
+
for (const [key, pending] of pendingChildSessionsByCallKey2.entries()) {
|
|
581
|
+
pending.delete(sessionId);
|
|
582
|
+
if (pending.size === 0) pendingChildSessionsByCallKey2.delete(key);
|
|
583
|
+
}
|
|
584
|
+
for (const key of [...taskContexts2.keys()]) {
|
|
585
|
+
if (key.startsWith(`${sessionId}:`)) taskContexts2.delete(key);
|
|
586
|
+
}
|
|
587
|
+
for (const [childSessionId, parentInfo] of [...mapChildSessionToParent2.entries()]) {
|
|
588
|
+
if (parentInfo.parentId === sessionId) {
|
|
589
|
+
mapChildSessionToParent2.delete(childSessionId);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return {
|
|
594
|
+
applyChildSessionParentToState,
|
|
595
|
+
attachChildSessionToParentTask,
|
|
596
|
+
cleanupSessionParentMap,
|
|
597
|
+
resolvePendingChildrenForCall
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
|
|
523
601
|
// src/tracing.ts
|
|
524
602
|
var sessions = /* @__PURE__ */ new Map();
|
|
603
|
+
var taskContexts = /* @__PURE__ */ new Map();
|
|
604
|
+
var runningTaskCallsBySession = /* @__PURE__ */ new Map();
|
|
605
|
+
var mapChildSessionToParent = /* @__PURE__ */ new Map();
|
|
606
|
+
var pendingChildSessionsByCallKey = /* @__PURE__ */ new Map();
|
|
607
|
+
function createSessionState(sessionId) {
|
|
608
|
+
return {
|
|
609
|
+
sessionId,
|
|
610
|
+
turnNumber: 0,
|
|
611
|
+
toolCount: 0,
|
|
612
|
+
currentInput: "",
|
|
613
|
+
outputParts: /* @__PURE__ */ new Map(),
|
|
614
|
+
reasoningParts: /* @__PURE__ */ new Map(),
|
|
615
|
+
toolSpanStarts: /* @__PURE__ */ new Map(),
|
|
616
|
+
processedMessages: /* @__PURE__ */ new Set()
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
function callKey(sessionId, callId) {
|
|
620
|
+
return `${sessionId}:${callId}`;
|
|
621
|
+
}
|
|
525
622
|
function getHostname() {
|
|
526
|
-
var _a
|
|
623
|
+
var _a;
|
|
527
624
|
try {
|
|
528
625
|
const bunGlobal = globalThis;
|
|
529
626
|
if ((_a = bunGlobal.Bun) == null ? void 0 : _a.hostname) return bunGlobal.Bun.hostname;
|
|
530
627
|
return require("os").hostname();
|
|
531
628
|
} catch (e) {
|
|
532
|
-
|
|
629
|
+
const h = process.env["HOSTNAME"];
|
|
630
|
+
if (h === void 0 || h === "") throw new Error("HOSTNAME env is not set");
|
|
631
|
+
return h;
|
|
533
632
|
}
|
|
534
633
|
}
|
|
535
|
-
function
|
|
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) {
|
|
634
|
+
function createHooks(config, worktree, directory, eventShipper, traceShipper) {
|
|
541
635
|
function log(msg, data) {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
}
|
|
550
|
-
})) == null ? void 0 : _a.catch) == null ? void 0 : _b.call(_a, () => {
|
|
551
|
-
});
|
|
636
|
+
const level = config.debug ? "info" : "debug";
|
|
637
|
+
const prefix = `[raindrop-ai/opencode-plugin] [${level}] ${msg}`;
|
|
638
|
+
if (data !== void 0) {
|
|
639
|
+
console.log(prefix, data);
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
console.log(prefix);
|
|
552
643
|
}
|
|
553
644
|
const hostname = getHostname();
|
|
554
645
|
const os = process.platform;
|
|
555
|
-
|
|
646
|
+
const {
|
|
647
|
+
applyChildSessionParentToState,
|
|
648
|
+
attachChildSessionToParentTask,
|
|
649
|
+
cleanupSessionParentMap,
|
|
650
|
+
resolvePendingChildrenForCall
|
|
651
|
+
} = createSessionParentMapHelpers({
|
|
652
|
+
sessions,
|
|
653
|
+
taskContexts,
|
|
654
|
+
runningTaskCallsBySession,
|
|
655
|
+
mapChildSessionToParent,
|
|
656
|
+
pendingChildSessionsByCallKey,
|
|
657
|
+
callKey,
|
|
658
|
+
log
|
|
659
|
+
});
|
|
556
660
|
return {
|
|
557
661
|
// ------------------------------------------------------------------
|
|
558
662
|
// event — session lifecycle + streaming parts
|
|
559
663
|
// ------------------------------------------------------------------
|
|
560
|
-
event: async ({
|
|
561
|
-
|
|
562
|
-
}) => {
|
|
563
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
664
|
+
event: async ({ event }) => {
|
|
665
|
+
var _a, _b, _c, _d, _e;
|
|
564
666
|
try {
|
|
565
667
|
const props = event.properties;
|
|
566
668
|
const info = props["info"];
|
|
567
|
-
const sessionID = props["sessionID"]
|
|
669
|
+
const sessionID = (_b = (_a = props["sessionID"]) != null ? _a : info == null ? void 0 : info["id"]) != null ? _b : props["id"];
|
|
670
|
+
const sessionEventTypes = ["session.created", "session.idle", "session.deleted", "session.error"];
|
|
671
|
+
if (sessionEventTypes.includes(event.type) && (sessionID === void 0 || sessionID === "")) {
|
|
672
|
+
throw new Error(`event ${event.type}: missing sessionID in properties`);
|
|
673
|
+
}
|
|
568
674
|
if (event.type === "session.created") {
|
|
569
|
-
if (
|
|
570
|
-
|
|
571
|
-
|
|
675
|
+
if (info == null || typeof info !== "object")
|
|
676
|
+
throw new Error("session.created: props.info is required");
|
|
677
|
+
const parentID = info["parentID"];
|
|
678
|
+
let state = sessions.get(sessionID);
|
|
679
|
+
if (!state) {
|
|
680
|
+
state = createSessionState(sessionID);
|
|
681
|
+
sessions.set(sessionID, state);
|
|
572
682
|
}
|
|
573
|
-
|
|
574
|
-
|
|
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 });
|
|
683
|
+
state.parentId = parentID;
|
|
684
|
+
if (state.parentId) mapChildSessionToParent.set(sessionID, { parentId: state.parentId });
|
|
603
685
|
} else if (event.type === "message.part.updated") {
|
|
604
686
|
const part = props["part"];
|
|
605
|
-
if (
|
|
606
|
-
|
|
607
|
-
const
|
|
608
|
-
|
|
687
|
+
if (part == null || typeof part !== "object")
|
|
688
|
+
throw new Error("message.part.updated: props.part is required");
|
|
689
|
+
const partObj = part;
|
|
690
|
+
const partSessionID = partObj["sessionID"];
|
|
691
|
+
const messageId = partObj["messageID"];
|
|
692
|
+
if (partSessionID == null || partSessionID === "")
|
|
693
|
+
throw new Error("message.part.updated: part.sessionID is required");
|
|
694
|
+
if (messageId == null || messageId === "")
|
|
695
|
+
throw new Error("message.part.updated: part.messageID is required");
|
|
609
696
|
const state = sessions.get(partSessionID);
|
|
610
697
|
if (!state) return;
|
|
611
|
-
if (
|
|
612
|
-
state.outputParts.set(messageId,
|
|
613
|
-
} else if (
|
|
614
|
-
const
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
if (
|
|
621
|
-
|
|
622
|
-
|
|
698
|
+
if (partObj["type"] === "text" && typeof partObj["text"] === "string") {
|
|
699
|
+
state.outputParts.set(messageId, partObj["text"]);
|
|
700
|
+
} else if (partObj["type"] === "tool" && messageId) {
|
|
701
|
+
const tool = partObj["tool"];
|
|
702
|
+
if (tool === "task") {
|
|
703
|
+
const callID = partObj["callID"];
|
|
704
|
+
const partState = partObj["state"];
|
|
705
|
+
if (callID == null || callID === "")
|
|
706
|
+
throw new Error("message.part.updated: part.callID is required for tool part");
|
|
707
|
+
if (partState == null || typeof partState !== "object")
|
|
708
|
+
throw new Error("message.part.updated: part.state is required for tool part");
|
|
709
|
+
const metadata = partState["metadata"];
|
|
710
|
+
const childSessionId = metadata != null && typeof metadata === "object" ? metadata["sessionId"] : void 0;
|
|
711
|
+
if (childSessionId != null && childSessionId !== "") {
|
|
712
|
+
mapChildSessionToParent.set(childSessionId, {
|
|
713
|
+
parentId: partSessionID
|
|
714
|
+
});
|
|
715
|
+
attachChildSessionToParentTask(
|
|
716
|
+
childSessionId,
|
|
717
|
+
partSessionID,
|
|
718
|
+
callID,
|
|
719
|
+
"part.metadata"
|
|
720
|
+
);
|
|
623
721
|
}
|
|
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
722
|
}
|
|
630
|
-
} else if (
|
|
631
|
-
state.reasoningParts.set(messageId,
|
|
632
|
-
} else {
|
|
633
|
-
log(`message.part.updated: skipping part type=${String(part["type"])}`);
|
|
723
|
+
} else if (partObj["type"] === "reasoning" && typeof partObj["text"] === "string") {
|
|
724
|
+
state.reasoningParts.set(messageId, partObj["text"]);
|
|
634
725
|
}
|
|
635
726
|
} else if (event.type === "message.updated") {
|
|
636
727
|
const msgInfo = props["info"];
|
|
637
|
-
if (
|
|
638
|
-
|
|
728
|
+
if (msgInfo == null || typeof msgInfo !== "object")
|
|
729
|
+
throw new Error("message.updated: props.info is required");
|
|
730
|
+
const info2 = msgInfo;
|
|
731
|
+
const role = info2["role"];
|
|
639
732
|
if (role !== "assistant") return;
|
|
640
|
-
const msgSessionID =
|
|
641
|
-
const messageId =
|
|
642
|
-
const time =
|
|
643
|
-
if (
|
|
733
|
+
const msgSessionID = info2["sessionID"];
|
|
734
|
+
const messageId = info2["id"];
|
|
735
|
+
const time = info2["time"];
|
|
736
|
+
if (msgSessionID == null || msgSessionID === "")
|
|
737
|
+
throw new Error("message.updated: info.sessionID is required");
|
|
738
|
+
if (messageId == null || messageId === "")
|
|
739
|
+
throw new Error("message.updated: info.id is required");
|
|
740
|
+
if (time == null || typeof time !== "object")
|
|
741
|
+
throw new Error("message.updated: info.time is required");
|
|
742
|
+
const timeObj = time;
|
|
743
|
+
if (timeObj["completed"] == null) return;
|
|
644
744
|
const state = sessions.get(msgSessionID);
|
|
645
|
-
if (!state || !state.currentEventId || !state.
|
|
745
|
+
if (!state || !state.currentEventId || !state.currentRootSpan) return;
|
|
646
746
|
if (state.processedMessages.has(messageId)) return;
|
|
647
747
|
state.processedMessages.add(messageId);
|
|
648
|
-
const tokens =
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
const
|
|
652
|
-
const
|
|
653
|
-
const
|
|
748
|
+
const tokens = info2["tokens"];
|
|
749
|
+
if (tokens == null || typeof tokens !== "object")
|
|
750
|
+
throw new Error("message.updated: info.tokens is required");
|
|
751
|
+
const tokensObj = tokens;
|
|
752
|
+
const inputTokens = tokensObj["input"];
|
|
753
|
+
const outputTokens = tokensObj["output"];
|
|
754
|
+
const reasoningTokens = tokensObj["reasoning"];
|
|
755
|
+
if (typeof inputTokens !== "number" || typeof outputTokens !== "number")
|
|
756
|
+
throw new Error("message.updated: tokens.input and tokens.output must be numbers");
|
|
757
|
+
const providerID = info2["providerID"];
|
|
758
|
+
const modelID = info2["modelID"];
|
|
759
|
+
if (typeof providerID !== "string" || providerID === "" || typeof modelID !== "string" || modelID === "")
|
|
760
|
+
throw new Error(
|
|
761
|
+
"message.updated: providerID and modelID are required non-empty strings"
|
|
762
|
+
);
|
|
654
763
|
const modelName = `${providerID}/${modelID}`;
|
|
655
|
-
|
|
656
|
-
const outputText =
|
|
657
|
-
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
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);
|
|
764
|
+
const finishReason = info2["finish"];
|
|
765
|
+
const outputText = state.outputParts.get(messageId);
|
|
766
|
+
const reasoningText = state.reasoningParts.get(messageId);
|
|
767
|
+
const isToolCallsCompletion = finishReason === "tool-calls";
|
|
768
|
+
const hasIntermediateReasoning = reasoningText !== void 0 && reasoningText.trim().length > 0;
|
|
769
|
+
if (outputText === void 0 && !(isToolCallsCompletion && hasIntermediateReasoning))
|
|
770
|
+
return;
|
|
771
|
+
if (isToolCallsCompletion && !hasIntermediateReasoning) {
|
|
772
|
+
await traceShipper.flush();
|
|
773
|
+
return;
|
|
675
774
|
}
|
|
676
|
-
const msgError =
|
|
677
|
-
const errorForSpan = msgError ?
|
|
775
|
+
const msgError = info2["error"];
|
|
776
|
+
const errorForSpan = msgError ? (() => {
|
|
777
|
+
var _a2, _b2;
|
|
778
|
+
const msg = (_b2 = (_a2 = msgError.data) == null ? void 0 : _a2.message) != null ? _b2 : msgError.name;
|
|
779
|
+
const name = msgError.name;
|
|
780
|
+
if (msg == null && name == null)
|
|
781
|
+
throw new Error("message.updated: error object must have name or data.message");
|
|
782
|
+
return `${msg != null ? msg : name}
|
|
678
783
|
|
|
679
|
-
type: ${
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
784
|
+
type: ${name != null ? name : "UnknownError"}`;
|
|
785
|
+
})() : void 0;
|
|
786
|
+
if (isToolCallsCompletion) {
|
|
787
|
+
if (reasoningText === void 0)
|
|
788
|
+
throw new Error("message.updated: reasoningParts missing for tool-call continuation");
|
|
789
|
+
const llmAttrs = [
|
|
790
|
+
attrString("ai.operationId", "generateText"),
|
|
791
|
+
attrString("ai.response.text", reasoningText),
|
|
792
|
+
attrString("gen_ai.system", providerID),
|
|
793
|
+
attrString("gen_ai.request.model", modelID),
|
|
794
|
+
attrInt("gen_ai.usage.input_tokens", inputTokens),
|
|
795
|
+
attrInt("gen_ai.usage.output_tokens", outputTokens)
|
|
796
|
+
];
|
|
797
|
+
if (reasoningTokens != null && typeof reasoningTokens === "number" && reasoningTokens > 0) {
|
|
798
|
+
llmAttrs.push(attrInt("gen_ai.usage.reasoning_tokens", reasoningTokens));
|
|
688
799
|
}
|
|
689
|
-
|
|
800
|
+
const llmParent = state.parentId && state.parentTaskSpanIds ? state.parentTaskSpanIds : state.currentRootSpan.ids;
|
|
801
|
+
const llmSpan = traceShipper.startSpan({
|
|
802
|
+
name: modelName,
|
|
803
|
+
parent: llmParent,
|
|
804
|
+
eventId: state.currentEventId,
|
|
805
|
+
attributes: llmAttrs
|
|
806
|
+
});
|
|
807
|
+
if (typeof timeObj["created"] === "number") {
|
|
808
|
+
llmSpan.startTimeUnixNano = String(timeObj["created"] * 1e6);
|
|
809
|
+
}
|
|
810
|
+
traceShipper.endSpan(llmSpan, { error: errorForSpan });
|
|
811
|
+
await traceShipper.flush();
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
if (outputText === void 0) return;
|
|
815
|
+
const rootSpan = state.currentRootSpan;
|
|
816
|
+
if (state.parentId) {
|
|
817
|
+
const llmAttrs = [
|
|
818
|
+
attrString("ai.operationId", "generateText"),
|
|
819
|
+
attrString("ai.prompt", state.currentInput),
|
|
820
|
+
attrString("ai.response.text", outputText),
|
|
821
|
+
attrString("gen_ai.system", providerID),
|
|
822
|
+
attrString("gen_ai.request.model", modelID),
|
|
823
|
+
attrInt("gen_ai.usage.input_tokens", inputTokens),
|
|
824
|
+
attrInt("gen_ai.usage.output_tokens", outputTokens)
|
|
825
|
+
];
|
|
826
|
+
if (reasoningTokens != null && typeof reasoningTokens === "number" && reasoningTokens > 0) {
|
|
827
|
+
llmAttrs.push(attrInt("gen_ai.usage.reasoning_tokens", reasoningTokens));
|
|
828
|
+
}
|
|
829
|
+
if (!state.parentTaskSpanIds) {
|
|
830
|
+
traceShipper.endSpan(rootSpan, {
|
|
831
|
+
error: "Missing strict parent task for child session"
|
|
832
|
+
});
|
|
833
|
+
state.currentRootSpan = void 0;
|
|
834
|
+
state.currentEventId = void 0;
|
|
835
|
+
await traceShipper.flush();
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
const finalLlmSpan = traceShipper.startSpan({
|
|
839
|
+
name: modelName,
|
|
840
|
+
parent: state.parentTaskSpanIds,
|
|
841
|
+
eventId: state.currentEventId,
|
|
842
|
+
attributes: llmAttrs
|
|
843
|
+
});
|
|
844
|
+
if (typeof timeObj["created"] === "number") {
|
|
845
|
+
finalLlmSpan.startTimeUnixNano = String(timeObj["created"] * 1e6);
|
|
846
|
+
}
|
|
847
|
+
traceShipper.endSpan(finalLlmSpan, { error: errorForSpan });
|
|
848
|
+
traceShipper.endSpan(rootSpan, {
|
|
849
|
+
attributes: [
|
|
850
|
+
attrString("is_subagent", "true"),
|
|
851
|
+
attrString("parent_session_id", state.parentId)
|
|
852
|
+
],
|
|
853
|
+
error: errorForSpan
|
|
854
|
+
});
|
|
855
|
+
} else {
|
|
856
|
+
rootSpan.name = modelName;
|
|
857
|
+
const rootAttrs = [
|
|
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
|
+
rootAttrs.push(attrInt("gen_ai.usage.reasoning_tokens", reasoningTokens));
|
|
868
|
+
}
|
|
869
|
+
traceShipper.endSpan(rootSpan, {
|
|
870
|
+
attributes: rootAttrs,
|
|
871
|
+
error: errorForSpan
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
state.currentRootSpan = void 0;
|
|
875
|
+
const hasAssistantResponse = outputText.trim().length > 0;
|
|
876
|
+
if (!hasAssistantResponse) {
|
|
877
|
+
await traceShipper.flush();
|
|
878
|
+
state.currentEventId = void 0;
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
if (!state.parentId) {
|
|
882
|
+
await eventShipper.finish(state.currentEventId, {
|
|
883
|
+
userId: state.sessionId,
|
|
884
|
+
model: modelName,
|
|
885
|
+
output: outputText,
|
|
886
|
+
properties: {
|
|
887
|
+
turn_count: state.turnNumber,
|
|
888
|
+
tool_count: state.toolCount
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
}
|
|
690
892
|
await traceShipper.flush();
|
|
691
|
-
|
|
692
|
-
messageId,
|
|
693
|
-
modelName,
|
|
694
|
-
inputTokens,
|
|
695
|
-
outputTokens,
|
|
696
|
-
reasoningTokens
|
|
697
|
-
});
|
|
893
|
+
state.currentEventId = void 0;
|
|
698
894
|
} else if (event.type === "session.idle") {
|
|
699
895
|
if (!sessionID) return;
|
|
700
896
|
const state = sessions.get(String(sessionID));
|
|
701
897
|
if (!state) return;
|
|
702
|
-
if (state.currentTurnSpan) {
|
|
703
|
-
traceShipper.endSpan(state.currentTurnSpan);
|
|
704
|
-
state.currentTurnSpan = void 0;
|
|
705
|
-
}
|
|
706
898
|
if (state.currentRootSpan) {
|
|
707
899
|
traceShipper.endSpan(state.currentRootSpan);
|
|
708
900
|
state.currentRootSpan = void 0;
|
|
709
901
|
}
|
|
710
902
|
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
903
|
state.currentEventId = void 0;
|
|
720
904
|
}
|
|
721
905
|
await traceShipper.flush();
|
|
722
|
-
|
|
906
|
+
if (state.parentId) {
|
|
907
|
+
cleanupSessionParentMap(String(sessionID));
|
|
908
|
+
sessions.delete(String(sessionID));
|
|
909
|
+
} else {
|
|
910
|
+
}
|
|
723
911
|
} else if (event.type === "session.deleted") {
|
|
724
912
|
if (!sessionID) return;
|
|
725
913
|
const state = sessions.get(String(sessionID));
|
|
726
914
|
if (!state) return;
|
|
727
|
-
if (state.currentTurnSpan) {
|
|
728
|
-
traceShipper.endSpan(state.currentTurnSpan);
|
|
729
|
-
state.currentTurnSpan = void 0;
|
|
730
|
-
}
|
|
731
915
|
if (state.currentRootSpan) {
|
|
732
916
|
traceShipper.endSpan(state.currentRootSpan);
|
|
733
917
|
state.currentRootSpan = void 0;
|
|
734
918
|
}
|
|
735
919
|
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
920
|
state.currentEventId = void 0;
|
|
746
921
|
}
|
|
922
|
+
cleanupSessionParentMap(String(sessionID));
|
|
747
923
|
await Promise.all([eventShipper.flush(), traceShipper.flush()]);
|
|
748
924
|
sessions.delete(String(sessionID));
|
|
749
|
-
log("Session deleted \u2014 flushed", { sessionID });
|
|
750
925
|
} else if (event.type === "session.error") {
|
|
751
|
-
const errorSessionID = props["sessionID"]
|
|
752
|
-
if (
|
|
926
|
+
const errorSessionID = (_c = props["sessionID"]) != null ? _c : sessionID;
|
|
927
|
+
if (errorSessionID === void 0 || errorSessionID === "")
|
|
928
|
+
throw new Error("session.error: missing sessionID");
|
|
753
929
|
const state = sessions.get(String(errorSessionID));
|
|
754
930
|
if (!state) return;
|
|
755
931
|
const errorObj = props["error"];
|
|
756
|
-
|
|
757
|
-
const
|
|
758
|
-
const
|
|
932
|
+
if (errorObj == null) throw new Error("session.error: missing error object");
|
|
933
|
+
const errorName = errorObj.name;
|
|
934
|
+
const errorMessage = (_e = (_d = errorObj.data) == null ? void 0 : _d.message) != null ? _e : errorObj.name;
|
|
935
|
+
if (errorName == null && errorMessage == null)
|
|
936
|
+
throw new Error("session.error: error object must have name or data.message");
|
|
937
|
+
const errorStr = `${errorMessage != null ? errorMessage : errorName}
|
|
759
938
|
|
|
760
|
-
type: ${errorName}`;
|
|
761
|
-
if (state.currentTurnSpan) {
|
|
762
|
-
traceShipper.endSpan(state.currentTurnSpan, { error: errorStr });
|
|
763
|
-
state.currentTurnSpan = void 0;
|
|
764
|
-
}
|
|
939
|
+
type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
765
940
|
if (state.currentRootSpan) {
|
|
766
941
|
traceShipper.endSpan(state.currentRootSpan, { error: errorStr });
|
|
767
942
|
state.currentRootSpan = void 0;
|
|
768
943
|
}
|
|
769
944
|
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
945
|
state.currentEventId = void 0;
|
|
779
946
|
}
|
|
947
|
+
cleanupSessionParentMap(String(errorSessionID));
|
|
780
948
|
await Promise.all([eventShipper.flush(), traceShipper.flush()]);
|
|
781
949
|
sessions.delete(String(errorSessionID));
|
|
782
|
-
log("Session error handled", { errorSessionID, errorName });
|
|
783
|
-
} else {
|
|
784
|
-
log(`unhandled event: ${event.type}`);
|
|
785
950
|
}
|
|
786
951
|
} catch (err) {
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
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
|
-
});
|
|
952
|
+
console.log(
|
|
953
|
+
`[raindrop-ai/opencode-plugin] [error] Error in event hook: ${err instanceof Error ? err.message : String(err)}`
|
|
954
|
+
);
|
|
795
955
|
}
|
|
796
956
|
},
|
|
797
957
|
// ------------------------------------------------------------------
|
|
798
958
|
// chat.message — user sent a message; start a new turn with a fresh event ID
|
|
799
959
|
// ------------------------------------------------------------------
|
|
800
960
|
"chat.message": async (messageInput, output) => {
|
|
801
|
-
var _a, _b;
|
|
802
961
|
try {
|
|
803
962
|
const { sessionID } = messageInput;
|
|
804
963
|
let state = sessions.get(sessionID);
|
|
805
964
|
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
|
-
};
|
|
965
|
+
state = createSessionState(sessionID);
|
|
823
966
|
sessions.set(sessionID, state);
|
|
824
967
|
}
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
state.
|
|
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
|
-
});
|
|
968
|
+
applyChildSessionParentToState(sessionID);
|
|
969
|
+
if (state.currentRootSpan) {
|
|
970
|
+
traceShipper.endSpan(state.currentRootSpan);
|
|
971
|
+
state.currentRootSpan = void 0;
|
|
840
972
|
}
|
|
841
973
|
state.turnNumber++;
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
974
|
+
if (output == null) throw new Error("chat.message: output is required");
|
|
975
|
+
const parts = output.parts;
|
|
976
|
+
if (parts == null) throw new Error("chat.message: output.parts is required");
|
|
845
977
|
const textParts = [];
|
|
846
978
|
const newAttachments = [];
|
|
847
979
|
for (const part of parts) {
|
|
@@ -852,26 +984,52 @@ type: ${errorName}`;
|
|
|
852
984
|
const filename = part["filename"];
|
|
853
985
|
const url = part["url"];
|
|
854
986
|
if (url && (mimeType == null ? void 0 : mimeType.startsWith("image/"))) {
|
|
987
|
+
if (filename === void 0 || filename === "")
|
|
988
|
+
throw new Error("chat.message: file part must have filename");
|
|
855
989
|
newAttachments.push({ type: "image", role: "input", name: filename, value: url });
|
|
856
990
|
}
|
|
857
991
|
}
|
|
858
992
|
}
|
|
859
993
|
const userText = textParts.join("\n");
|
|
860
994
|
state.currentInput = userText;
|
|
861
|
-
if (
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
995
|
+
if (state.parentId) {
|
|
996
|
+
if (!state.parentTaskSpanIds || !state.parentEventContext) {
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
state.currentEventId = state.parentEventContext.eventId;
|
|
1000
|
+
state.currentRootSpan = traceShipper.startSpan({
|
|
1001
|
+
name: "Subagent",
|
|
1002
|
+
parent: state.parentTaskSpanIds,
|
|
1003
|
+
eventId: state.currentEventId,
|
|
1004
|
+
attributes: [
|
|
1005
|
+
attrString("workspace", worktree),
|
|
1006
|
+
attrString("directory", directory),
|
|
1007
|
+
attrString("hostname", hostname),
|
|
1008
|
+
attrString("os", os),
|
|
1009
|
+
attrString("is_subagent", "true"),
|
|
1010
|
+
attrString("parent_session_id", state.parentId)
|
|
1011
|
+
]
|
|
1012
|
+
});
|
|
1013
|
+
return;
|
|
1014
|
+
}
|
|
1015
|
+
state.currentEventId = generateId();
|
|
1016
|
+
state.currentRootSpan = traceShipper.startSpan({
|
|
1017
|
+
name: "ai.event",
|
|
865
1018
|
eventId: state.currentEventId,
|
|
866
|
-
attributes: [
|
|
1019
|
+
attributes: [
|
|
1020
|
+
attrString("workspace", worktree),
|
|
1021
|
+
attrString("directory", directory),
|
|
1022
|
+
attrString("hostname", hostname),
|
|
1023
|
+
attrString("os", os)
|
|
1024
|
+
]
|
|
867
1025
|
});
|
|
868
1026
|
await eventShipper.patch(state.currentEventId, {
|
|
869
1027
|
isPending: true,
|
|
870
|
-
userId: state.
|
|
1028
|
+
userId: state.sessionId,
|
|
871
1029
|
convoId: state.sessionId,
|
|
872
1030
|
eventName: config.eventName,
|
|
873
|
-
input: userText
|
|
874
|
-
|
|
1031
|
+
input: userText,
|
|
1032
|
+
...newAttachments.length > 0 ? { attachments: newAttachments } : {},
|
|
875
1033
|
properties: {
|
|
876
1034
|
workspace: worktree,
|
|
877
1035
|
directory,
|
|
@@ -881,74 +1039,137 @@ type: ${errorName}`;
|
|
|
881
1039
|
tool_count: state.toolCount
|
|
882
1040
|
}
|
|
883
1041
|
});
|
|
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
1042
|
} catch (err) {
|
|
892
|
-
log("Error in chat.message hook", { error: String(err) });
|
|
893
1043
|
}
|
|
894
1044
|
},
|
|
895
1045
|
// ------------------------------------------------------------------
|
|
896
1046
|
// tool.execute.before — record start time only (span created atomically in after)
|
|
1047
|
+
// For child sessions, tool spans are parented directly to the parent task span.
|
|
897
1048
|
// ------------------------------------------------------------------
|
|
898
1049
|
"tool.execute.before": async (toolInput, _output) => {
|
|
1050
|
+
var _a;
|
|
899
1051
|
try {
|
|
900
1052
|
const { tool, sessionID, callID } = toolInput;
|
|
901
1053
|
const state = sessions.get(sessionID);
|
|
902
|
-
if (!state || !state.
|
|
903
|
-
state.
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
1054
|
+
if (!state || !state.currentEventId) return;
|
|
1055
|
+
const spanParent = state.parentId && state.parentTaskSpanIds ? state.parentTaskSpanIds : (_a = state.currentRootSpan) == null ? void 0 : _a.ids;
|
|
1056
|
+
if (!spanParent) return;
|
|
1057
|
+
const startTimeUnixNano = nowUnixNanoString();
|
|
1058
|
+
if (tool === "task") {
|
|
1059
|
+
const liveTaskSpan = traceShipper.startSpan({
|
|
1060
|
+
name: "ai.toolCall",
|
|
1061
|
+
parent: spanParent,
|
|
1062
|
+
eventId: state.currentEventId,
|
|
1063
|
+
attributes: [
|
|
1064
|
+
attrString("ai.operationId", "ai.toolCall"),
|
|
1065
|
+
attrString("ai.toolCall.name", tool),
|
|
1066
|
+
attrString("ai.toolCall.id", callID)
|
|
1067
|
+
]
|
|
1068
|
+
});
|
|
1069
|
+
liveTaskSpan.startTimeUnixNano = startTimeUnixNano;
|
|
1070
|
+
const ctx = {
|
|
1071
|
+
ids: liveTaskSpan.ids,
|
|
1072
|
+
eventContext: {
|
|
1073
|
+
eventId: state.currentEventId,
|
|
1074
|
+
userId: state.sessionId,
|
|
1075
|
+
convoId: state.sessionId
|
|
1076
|
+
}
|
|
1077
|
+
};
|
|
1078
|
+
taskContexts.set(callKey(sessionID, callID), ctx);
|
|
1079
|
+
let running = runningTaskCallsBySession.get(sessionID);
|
|
1080
|
+
if (running == null) {
|
|
1081
|
+
running = /* @__PURE__ */ new Set();
|
|
1082
|
+
runningTaskCallsBySession.set(sessionID, running);
|
|
1083
|
+
}
|
|
1084
|
+
running.add(callID);
|
|
1085
|
+
resolvePendingChildrenForCall(sessionID, callID, "tool.execute.before");
|
|
1086
|
+
state.toolSpanStarts.set(callID, {
|
|
1087
|
+
startTimeUnixNano,
|
|
1088
|
+
parent: spanParent,
|
|
1089
|
+
eventId: state.currentEventId,
|
|
1090
|
+
name: tool,
|
|
1091
|
+
liveSpan: liveTaskSpan
|
|
1092
|
+
});
|
|
1093
|
+
} else {
|
|
1094
|
+
state.toolSpanStarts.set(callID, {
|
|
1095
|
+
startTimeUnixNano,
|
|
1096
|
+
parent: spanParent,
|
|
1097
|
+
eventId: state.currentEventId,
|
|
1098
|
+
name: tool
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
911
1101
|
} catch (err) {
|
|
912
|
-
log("Error in tool.execute.before hook", { error: String(err) });
|
|
913
1102
|
}
|
|
914
1103
|
},
|
|
915
1104
|
// ------------------------------------------------------------------
|
|
916
1105
|
// tool.execute.after — create tool span atomically with both start and end times
|
|
917
1106
|
// ------------------------------------------------------------------
|
|
918
1107
|
"tool.execute.after": async (toolInput, result) => {
|
|
919
|
-
var _a;
|
|
920
1108
|
try {
|
|
921
|
-
const { tool, sessionID, callID } = toolInput;
|
|
1109
|
+
const { tool, sessionID, callID, args } = toolInput;
|
|
922
1110
|
const state = sessions.get(sessionID);
|
|
923
1111
|
if (!state || !state.currentEventId) return;
|
|
924
1112
|
state.toolCount++;
|
|
925
|
-
|
|
926
|
-
|
|
1113
|
+
if (args === void 0 || args === null)
|
|
1114
|
+
throw new Error("tool.execute.after: args is required");
|
|
1115
|
+
const toolCallArgs = typeof args === "string" ? args : JSON.stringify(args);
|
|
927
1116
|
const startInfo = state.toolSpanStarts.get(callID);
|
|
928
1117
|
state.toolSpanStarts.delete(callID);
|
|
1118
|
+
const resultMetadata = result.metadata;
|
|
1119
|
+
const childSessionId = resultMetadata == null ? void 0 : resultMetadata["sessionId"];
|
|
1120
|
+
if (tool === "task" && childSessionId) {
|
|
1121
|
+
mapChildSessionToParent.set(childSessionId, { parentId: sessionID });
|
|
1122
|
+
attachChildSessionToParentTask(childSessionId, sessionID, callID, "tool.execute.after");
|
|
1123
|
+
}
|
|
929
1124
|
if (startInfo) {
|
|
930
1125
|
const endTimeUnixNano = nowUnixNanoString();
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
1126
|
+
if (!("output" in result) || result.output === void 0)
|
|
1127
|
+
throw new Error("tool.execute.after: result.output is required");
|
|
1128
|
+
let toolResult;
|
|
1129
|
+
try {
|
|
1130
|
+
toolResult = JSON.stringify(result.output);
|
|
1131
|
+
} catch (err) {
|
|
1132
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1133
|
+
throw new Error(`tool.execute.after: result.output must be JSON-serializable: ${msg}`);
|
|
1134
|
+
}
|
|
1135
|
+
if (startInfo.liveSpan) {
|
|
1136
|
+
traceShipper.endSpan(startInfo.liveSpan, {
|
|
1137
|
+
attributes: [
|
|
1138
|
+
attrString("ai.toolCall.args", toolCallArgs),
|
|
1139
|
+
attrString("ai.toolCall.result", toolResult)
|
|
1140
|
+
]
|
|
1141
|
+
});
|
|
1142
|
+
} else {
|
|
1143
|
+
traceShipper.createSpan({
|
|
1144
|
+
name: "ai.toolCall",
|
|
1145
|
+
parent: startInfo.parent,
|
|
1146
|
+
eventId: startInfo.eventId,
|
|
1147
|
+
startTimeUnixNano: startInfo.startTimeUnixNano,
|
|
1148
|
+
endTimeUnixNano,
|
|
1149
|
+
attributes: [
|
|
1150
|
+
attrString("ai.operationId", "ai.toolCall"),
|
|
1151
|
+
attrString("ai.toolCall.name", tool),
|
|
1152
|
+
attrString("ai.toolCall.id", callID),
|
|
1153
|
+
attrString("ai.toolCall.args", toolCallArgs),
|
|
1154
|
+
attrString("ai.toolCall.result", toolResult)
|
|
1155
|
+
]
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
if (tool === "task") {
|
|
1160
|
+
const running = runningTaskCallsBySession.get(sessionID);
|
|
1161
|
+
if (running) {
|
|
1162
|
+
running.delete(callID);
|
|
1163
|
+
if (running.size === 0) runningTaskCallsBySession.delete(sessionID);
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
if (!state.parentId) {
|
|
1167
|
+
await eventShipper.patch(state.currentEventId, {
|
|
1168
|
+
userId: state.sessionId,
|
|
1169
|
+
properties: { tool_count: state.toolCount }
|
|
943
1170
|
});
|
|
944
1171
|
}
|
|
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
1172
|
} catch (err) {
|
|
951
|
-
log("Error in tool.execute.after hook", { error: String(err) });
|
|
952
1173
|
}
|
|
953
1174
|
}
|
|
954
1175
|
};
|
|
@@ -957,18 +1178,9 @@ type: ${errorName}`;
|
|
|
957
1178
|
// src/index.ts
|
|
958
1179
|
async function plugin(input) {
|
|
959
1180
|
var _a;
|
|
960
|
-
const { client } = input;
|
|
961
1181
|
const config = loadConfig(input.directory);
|
|
962
1182
|
function appLog(level, message) {
|
|
963
|
-
|
|
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
|
-
});
|
|
1183
|
+
console.log(`[raindrop-ai/opencode-plugin] [${level}] ${message}`);
|
|
972
1184
|
}
|
|
973
1185
|
if (!config.writeKey) {
|
|
974
1186
|
appLog(
|
|
@@ -992,7 +1204,6 @@ async function plugin(input) {
|
|
|
992
1204
|
});
|
|
993
1205
|
const worktree = (_a = input.worktree) != null ? _a : input.directory;
|
|
994
1206
|
const hooks = createHooks(
|
|
995
|
-
client,
|
|
996
1207
|
config,
|
|
997
1208
|
worktree,
|
|
998
1209
|
input.directory,
|