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