@raindrop-ai/opencode-plugin 0.1.3 → 0.2.0
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/README.md +27 -2
- package/dist/chunk-4DAI6CYK.js +2573 -0
- package/dist/index.cjs +1147 -253
- package/dist/index.d-vgBcEZbX.d.cts +89 -0
- package/dist/index.d-vgBcEZbX.d.ts +89 -0
- package/dist/index.d.cts +6 -69
- package/dist/index.d.ts +6 -69
- package/dist/index.js +47 -1695
- package/dist/v2.cjs +3327 -0
- package/dist/v2.d.cts +41 -0
- package/dist/v2.d.ts +41 -0
- package/dist/v2.js +761 -0
- package/package.json +23 -4
package/dist/v2.js
ADDED
|
@@ -0,0 +1,761 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EventShipper,
|
|
3
|
+
PLUGIN_NAME,
|
|
4
|
+
PLUGIN_VERSION,
|
|
5
|
+
TraceShipper,
|
|
6
|
+
attrString,
|
|
7
|
+
boundedStringify,
|
|
8
|
+
buildLlmGenAiAttributes,
|
|
9
|
+
capText,
|
|
10
|
+
createAppGitContext,
|
|
11
|
+
generateId,
|
|
12
|
+
loadConfig,
|
|
13
|
+
resolveLocalDebuggerBaseUrl
|
|
14
|
+
} from "./chunk-4DAI6CYK.js";
|
|
15
|
+
|
|
16
|
+
// src/v2.ts
|
|
17
|
+
import { Plugin } from "@opencode/plugin";
|
|
18
|
+
|
|
19
|
+
// src/tracing-v2.ts
|
|
20
|
+
import { hostname as osHostname } from "os";
|
|
21
|
+
var ERROR_LOG_INTERVAL_MS = 3e4;
|
|
22
|
+
var lastErrorLogAt = /* @__PURE__ */ new Map();
|
|
23
|
+
function rateLimitedErrorLog(key, message) {
|
|
24
|
+
const now = Date.now();
|
|
25
|
+
const last = lastErrorLogAt.get(key);
|
|
26
|
+
if (last !== void 0 && now - last < ERROR_LOG_INTERVAL_MS) return;
|
|
27
|
+
lastErrorLogAt.set(key, now);
|
|
28
|
+
console.log(message);
|
|
29
|
+
}
|
|
30
|
+
function unwrapQuotes(s) {
|
|
31
|
+
if (s.length < 2) return s;
|
|
32
|
+
const first = s[0];
|
|
33
|
+
const last = s[s.length - 1];
|
|
34
|
+
if ((first === '"' || first === "'") && first === last) return s.slice(1, -1);
|
|
35
|
+
return s;
|
|
36
|
+
}
|
|
37
|
+
function buildPromptMessages(systemPrompt, userInput) {
|
|
38
|
+
const messages = [];
|
|
39
|
+
if (systemPrompt) messages.push({ role: "system", content: systemPrompt });
|
|
40
|
+
messages.push({ role: "user", content: unwrapQuotes(userInput) });
|
|
41
|
+
return JSON.stringify(messages);
|
|
42
|
+
}
|
|
43
|
+
function getHostname() {
|
|
44
|
+
var _a;
|
|
45
|
+
try {
|
|
46
|
+
const bunGlobal = globalThis;
|
|
47
|
+
if ((_a = bunGlobal.Bun) == null ? void 0 : _a.hostname) return bunGlobal.Bun.hostname;
|
|
48
|
+
return osHostname();
|
|
49
|
+
} catch (e) {
|
|
50
|
+
const h = process.env["HOSTNAME"];
|
|
51
|
+
if (h === void 0 || h === "") throw new Error("HOSTNAME env is not set");
|
|
52
|
+
return h;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function msToNs(ms) {
|
|
56
|
+
return `${String(Math.floor(ms))}000000`;
|
|
57
|
+
}
|
|
58
|
+
var IMAGE_NAME_RE = /\.(png|jpe?g|gif|webp|bmp|avif|svg)$/i;
|
|
59
|
+
function isImageAttachment(file) {
|
|
60
|
+
var _a;
|
|
61
|
+
if (file.uri.startsWith("data:image/")) return true;
|
|
62
|
+
return IMAGE_NAME_RE.test((_a = file.name) != null ? _a : "") || IMAGE_NAME_RE.test(file.uri);
|
|
63
|
+
}
|
|
64
|
+
function errorString(error) {
|
|
65
|
+
return `${error.message}
|
|
66
|
+
|
|
67
|
+
type: ${error.type}`;
|
|
68
|
+
}
|
|
69
|
+
function createV2Tracing(config, eventShipper, traceShipper, sessionLookup) {
|
|
70
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
71
|
+
const admittedPrompts = /* @__PURE__ */ new Map();
|
|
72
|
+
const subagentLinks = /* @__PURE__ */ new Map();
|
|
73
|
+
const hostname = getHostname();
|
|
74
|
+
const os = process.platform;
|
|
75
|
+
function getOrCreateState(sessionID) {
|
|
76
|
+
let state = sessions.get(sessionID);
|
|
77
|
+
if (!state) {
|
|
78
|
+
state = {
|
|
79
|
+
sessionId: sessionID,
|
|
80
|
+
input: "",
|
|
81
|
+
outputParts: /* @__PURE__ */ new Map(),
|
|
82
|
+
reasoningParts: /* @__PURE__ */ new Map(),
|
|
83
|
+
turnOutputParts: [],
|
|
84
|
+
turnOutputTokens: 0,
|
|
85
|
+
turnInputTokens: 0,
|
|
86
|
+
stepSpans: /* @__PURE__ */ new Map(),
|
|
87
|
+
toolStarts: /* @__PURE__ */ new Map(),
|
|
88
|
+
toolNames: /* @__PURE__ */ new Map()
|
|
89
|
+
};
|
|
90
|
+
sessions.set(sessionID, state);
|
|
91
|
+
}
|
|
92
|
+
return state;
|
|
93
|
+
}
|
|
94
|
+
function unlinkedSubagentParent(parent) {
|
|
95
|
+
var _a, _b;
|
|
96
|
+
const running = [...parent.toolStarts.values()].at(-1);
|
|
97
|
+
return (_b = running == null ? void 0 : running.span.ids) != null ? _b : (_a = parent.rootSpan) == null ? void 0 : _a.ids;
|
|
98
|
+
}
|
|
99
|
+
function systemPromptAttrs(state) {
|
|
100
|
+
if (!state.systemPrompt) return [];
|
|
101
|
+
return [
|
|
102
|
+
attrString("gen_ai.prompt.0.role", "system"),
|
|
103
|
+
attrString("gen_ai.prompt.0.content", state.systemPrompt)
|
|
104
|
+
];
|
|
105
|
+
}
|
|
106
|
+
async function finalizeTurn(state, error) {
|
|
107
|
+
var _a, _b, _c;
|
|
108
|
+
for (const step of state.stepSpans.values()) {
|
|
109
|
+
traceShipper.endSpan(step.span);
|
|
110
|
+
}
|
|
111
|
+
state.stepSpans.clear();
|
|
112
|
+
if (state.compactionSpan) {
|
|
113
|
+
traceShipper.endSpan(state.compactionSpan);
|
|
114
|
+
state.compactionSpan = void 0;
|
|
115
|
+
}
|
|
116
|
+
for (const start of state.toolStarts.values()) {
|
|
117
|
+
traceShipper.endSpan(start.span);
|
|
118
|
+
}
|
|
119
|
+
state.toolStarts.clear();
|
|
120
|
+
state.outputParts.clear();
|
|
121
|
+
state.reasoningParts.clear();
|
|
122
|
+
state.toolNames.clear();
|
|
123
|
+
if (state.rootSpan) {
|
|
124
|
+
const attrs = [];
|
|
125
|
+
if (state.parentId) {
|
|
126
|
+
attrs.push(
|
|
127
|
+
attrString("is_subagent", "true"),
|
|
128
|
+
attrString("parent_session_id", state.parentId)
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
attrs.push(attrString("message_id", state.lastAssistantMessageId));
|
|
132
|
+
traceShipper.endSpan(state.rootSpan, {
|
|
133
|
+
attributes: attrs,
|
|
134
|
+
error
|
|
135
|
+
});
|
|
136
|
+
state.rootSpan = void 0;
|
|
137
|
+
}
|
|
138
|
+
const eventId = state.eventId;
|
|
139
|
+
state.eventId = void 0;
|
|
140
|
+
if (eventId && !state.parentId) {
|
|
141
|
+
const turnOutput = capText(state.turnOutputParts.join("\n\n"));
|
|
142
|
+
await eventShipper.finish(eventId, {
|
|
143
|
+
userId: (_b = (_a = state.eventMetadata) == null ? void 0 : _a.userId) != null ? _b : state.sessionId,
|
|
144
|
+
model: (_c = state.lastModel) == null ? void 0 : _c.modelID,
|
|
145
|
+
output: turnOutput.length > 0 ? turnOutput : void 0,
|
|
146
|
+
usage: {
|
|
147
|
+
promptTokens: state.turnInputTokens,
|
|
148
|
+
completionTokens: state.turnOutputTokens
|
|
149
|
+
},
|
|
150
|
+
error: error ? { message: error } : void 0,
|
|
151
|
+
properties: {
|
|
152
|
+
plugin_version: PLUGIN_VERSION,
|
|
153
|
+
opencode_version: "v2",
|
|
154
|
+
...state.lastModel ? { "ai.provider": state.lastModel.providerID } : {},
|
|
155
|
+
...state.lastAssistantMessageId ? { message_id: state.lastAssistantMessageId } : {}
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
await Promise.all([eventShipper.flush(), traceShipper.flush()]);
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
currentEventId(sessionID) {
|
|
163
|
+
var _a;
|
|
164
|
+
return (_a = sessions.get(sessionID)) == null ? void 0 : _a.eventId;
|
|
165
|
+
},
|
|
166
|
+
// ------------------------------------------------------------------
|
|
167
|
+
// prompt hook — record the admitted prompt; the turn opens on delivery
|
|
168
|
+
// ------------------------------------------------------------------
|
|
169
|
+
onPrompt(input, directory) {
|
|
170
|
+
var _a, _b, _c;
|
|
171
|
+
try {
|
|
172
|
+
const textParts = [];
|
|
173
|
+
const attachments = [];
|
|
174
|
+
const prompt = input.prompt;
|
|
175
|
+
if (typeof prompt.text === "string" && prompt.text.length > 0) {
|
|
176
|
+
textParts.push(prompt.text);
|
|
177
|
+
}
|
|
178
|
+
for (const file of (_a = prompt.files) != null ? _a : []) {
|
|
179
|
+
if (isImageAttachment(file)) {
|
|
180
|
+
attachments.push({
|
|
181
|
+
type: "image",
|
|
182
|
+
role: "input",
|
|
183
|
+
name: file.name,
|
|
184
|
+
value: file.uri
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const baseMeta = { ...config.eventMetadata };
|
|
189
|
+
const promptMeta = {};
|
|
190
|
+
const meta = input.metadata;
|
|
191
|
+
if (meta != null && typeof meta === "object") {
|
|
192
|
+
if (typeof meta["userId"] === "string") promptMeta.userId = meta["userId"];
|
|
193
|
+
if (typeof meta["eventName"] === "string") promptMeta.eventName = meta["eventName"];
|
|
194
|
+
if (meta["properties"] != null && typeof meta["properties"] === "object")
|
|
195
|
+
promptMeta.properties = meta["properties"];
|
|
196
|
+
}
|
|
197
|
+
admittedPrompts.set(input.messageID, {
|
|
198
|
+
sessionID: input.sessionID,
|
|
199
|
+
directory,
|
|
200
|
+
text: capText(textParts.join("\n")),
|
|
201
|
+
attachments,
|
|
202
|
+
eventMetadata: {
|
|
203
|
+
...baseMeta,
|
|
204
|
+
...promptMeta,
|
|
205
|
+
properties: baseMeta.properties || promptMeta.properties ? { ...(_b = baseMeta.properties) != null ? _b : {}, ...(_c = promptMeta.properties) != null ? _c : {} } : void 0
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
} catch (err) {
|
|
209
|
+
rateLimitedErrorLog(
|
|
210
|
+
"prompt",
|
|
211
|
+
`[raindrop-ai/opencode-plugin] [error] Error in prompt hook: ${err instanceof Error ? err.message : String(err)}`
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
onContext(input) {
|
|
216
|
+
onContextImpl(input);
|
|
217
|
+
},
|
|
218
|
+
onEvent(event) {
|
|
219
|
+
return onEventImpl(event);
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
async function startTurn(prompt, deliveredAt) {
|
|
223
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
224
|
+
const sessionID = prompt.sessionID;
|
|
225
|
+
const directory = prompt.directory;
|
|
226
|
+
const state = getOrCreateState(sessionID);
|
|
227
|
+
const link = subagentLinks.get(sessionID);
|
|
228
|
+
subagentLinks.delete(sessionID);
|
|
229
|
+
if (link) state.parentId = link.parentSessionId;
|
|
230
|
+
if (state.parentId === void 0 && sessionLookup) {
|
|
231
|
+
try {
|
|
232
|
+
const info = await sessionLookup(sessionID);
|
|
233
|
+
if (info == null ? void 0 : info.parentID) state.parentId = info.parentID;
|
|
234
|
+
} catch (e) {
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (state.eventId || state.rootSpan) {
|
|
238
|
+
await finalizeTurn(state);
|
|
239
|
+
}
|
|
240
|
+
state.eventMetadata = prompt.eventMetadata;
|
|
241
|
+
const userText = prompt.text;
|
|
242
|
+
const newAttachments = prompt.attachments;
|
|
243
|
+
state.input = userText;
|
|
244
|
+
state.turnOutputParts = [];
|
|
245
|
+
state.turnOutputTokens = 0;
|
|
246
|
+
state.turnInputTokens = 0;
|
|
247
|
+
state.lastModel = void 0;
|
|
248
|
+
state.lastAssistantMessageId = void 0;
|
|
249
|
+
state.outputParts.clear();
|
|
250
|
+
state.reasoningParts.clear();
|
|
251
|
+
state.toolNames.clear();
|
|
252
|
+
if (state.parentId) {
|
|
253
|
+
const parent = sessions.get(state.parentId);
|
|
254
|
+
const eventId = (_a = link == null ? void 0 : link.eventId) != null ? _a : parent == null ? void 0 : parent.eventId;
|
|
255
|
+
const parentSpan = (_b = link == null ? void 0 : link.toolSpan) != null ? _b : parent ? unlinkedSubagentParent(parent) : void 0;
|
|
256
|
+
if (!eventId || !parentSpan) return;
|
|
257
|
+
state.eventId = eventId;
|
|
258
|
+
state.rootSpan = traceShipper.startSpan({
|
|
259
|
+
name: "Subagent",
|
|
260
|
+
parent: parentSpan,
|
|
261
|
+
eventId: state.eventId,
|
|
262
|
+
startTimeUnixNano: msToNs(deliveredAt),
|
|
263
|
+
operationId: "ai.subagent",
|
|
264
|
+
attributes: [
|
|
265
|
+
attrString("workspace", directory),
|
|
266
|
+
attrString("directory", directory),
|
|
267
|
+
attrString("hostname", hostname),
|
|
268
|
+
attrString("os", os),
|
|
269
|
+
attrString("is_subagent", "true"),
|
|
270
|
+
attrString("parent_session_id", state.parentId)
|
|
271
|
+
]
|
|
272
|
+
});
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
state.eventId = generateId();
|
|
276
|
+
state.rootSpan = traceShipper.startSpan({
|
|
277
|
+
name: "ai.event",
|
|
278
|
+
eventId: state.eventId,
|
|
279
|
+
startTimeUnixNano: msToNs(deliveredAt),
|
|
280
|
+
operationId: "ai.event",
|
|
281
|
+
attributes: [
|
|
282
|
+
attrString("workspace", directory),
|
|
283
|
+
attrString("directory", directory),
|
|
284
|
+
attrString("hostname", hostname),
|
|
285
|
+
attrString("os", os)
|
|
286
|
+
]
|
|
287
|
+
});
|
|
288
|
+
await eventShipper.patch(state.eventId, {
|
|
289
|
+
isPending: true,
|
|
290
|
+
userId: (_d = (_c = state.eventMetadata) == null ? void 0 : _c.userId) != null ? _d : state.sessionId,
|
|
291
|
+
convoId: state.sessionId,
|
|
292
|
+
eventName: (_f = (_e = state.eventMetadata) == null ? void 0 : _e.eventName) != null ? _f : config.eventName,
|
|
293
|
+
input: userText,
|
|
294
|
+
...newAttachments.length > 0 ? { attachments: newAttachments } : {},
|
|
295
|
+
properties: {
|
|
296
|
+
workspace: directory,
|
|
297
|
+
directory,
|
|
298
|
+
hostname,
|
|
299
|
+
os,
|
|
300
|
+
plugin_version: PLUGIN_VERSION,
|
|
301
|
+
opencode_version: "v2",
|
|
302
|
+
...(_g = state.eventMetadata) == null ? void 0 : _g.properties
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
function onContextImpl(input) {
|
|
307
|
+
try {
|
|
308
|
+
if (!config.captureSystemPrompt) return;
|
|
309
|
+
const state = getOrCreateState(input.sessionID);
|
|
310
|
+
const MAX_SYSTEM_PROMPT_LENGTH = 32768;
|
|
311
|
+
const TRUNCATION_MARKER = "\n...[truncated]";
|
|
312
|
+
const joined = input.system.map((part) => part.text).join("\n\n");
|
|
313
|
+
state.systemPrompt = joined.length > MAX_SYSTEM_PROMPT_LENGTH ? joined.slice(0, MAX_SYSTEM_PROMPT_LENGTH - TRUNCATION_MARKER.length) + TRUNCATION_MARKER : joined;
|
|
314
|
+
} catch (err) {
|
|
315
|
+
rateLimitedErrorLog(
|
|
316
|
+
"context",
|
|
317
|
+
`[raindrop-ai/opencode-plugin] [error] Error in context hook: ${err instanceof Error ? err.message : String(err)}`
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
async function onEventImpl(event) {
|
|
322
|
+
var _a, _b, _c, _d, _e;
|
|
323
|
+
switch (event.type) {
|
|
324
|
+
case "session.created": {
|
|
325
|
+
const data = event.data;
|
|
326
|
+
const state = getOrCreateState(data.sessionID);
|
|
327
|
+
state.parentId = data.parentID;
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
case "session.inbox.delivered": {
|
|
331
|
+
const prompt = admittedPrompts.get(event.data.inboxID);
|
|
332
|
+
if (!prompt) return;
|
|
333
|
+
admittedPrompts.delete(event.data.inboxID);
|
|
334
|
+
await startTurn(prompt, event.created);
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
case "session.inbox.cancelled": {
|
|
338
|
+
admittedPrompts.delete(event.data.inboxID);
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
case "session.step.started": {
|
|
342
|
+
const data = event.data;
|
|
343
|
+
const state = sessions.get(data.sessionID);
|
|
344
|
+
if (!state || !state.eventId) return;
|
|
345
|
+
const parent = (_a = state.rootSpan) == null ? void 0 : _a.ids;
|
|
346
|
+
if (!parent) return;
|
|
347
|
+
const span = traceShipper.startSpan({
|
|
348
|
+
name: `${data.model.providerID}/${data.model.id}`,
|
|
349
|
+
parent,
|
|
350
|
+
eventId: state.eventId,
|
|
351
|
+
startTimeUnixNano: msToNs(data.started),
|
|
352
|
+
attributes: [
|
|
353
|
+
attrString("ai.operationId", "generateText"),
|
|
354
|
+
attrString("ai.prompt", unwrapQuotes(state.input)),
|
|
355
|
+
attrString(
|
|
356
|
+
"ai.prompt.messages",
|
|
357
|
+
buildPromptMessages(state.systemPrompt, state.input)
|
|
358
|
+
)
|
|
359
|
+
]
|
|
360
|
+
});
|
|
361
|
+
state.stepSpans.set(data.assistantMessageID, {
|
|
362
|
+
span,
|
|
363
|
+
providerID: data.model.providerID,
|
|
364
|
+
modelID: data.model.id
|
|
365
|
+
});
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
case "session.step.ended": {
|
|
369
|
+
const data = event.data;
|
|
370
|
+
const state = sessions.get(data.sessionID);
|
|
371
|
+
const step = state == null ? void 0 : state.stepSpans.get(data.assistantMessageID);
|
|
372
|
+
if (!state || !step) return;
|
|
373
|
+
state.stepSpans.delete(data.assistantMessageID);
|
|
374
|
+
const text = state.outputParts.get(data.assistantMessageID);
|
|
375
|
+
if (text !== void 0 && text.trim().length > 0) {
|
|
376
|
+
state.turnOutputParts.push(text);
|
|
377
|
+
}
|
|
378
|
+
state.turnOutputTokens += data.tokens.output;
|
|
379
|
+
state.turnInputTokens = data.tokens.input;
|
|
380
|
+
state.lastModel = { providerID: step.providerID, modelID: step.modelID };
|
|
381
|
+
state.lastAssistantMessageId = data.assistantMessageID;
|
|
382
|
+
const attrs = [
|
|
383
|
+
attrString("ai.response.text", text !== void 0 ? capText(text) : void 0),
|
|
384
|
+
...systemPromptAttrs(state),
|
|
385
|
+
...buildLlmGenAiAttributes({
|
|
386
|
+
providerID: step.providerID,
|
|
387
|
+
modelID: step.modelID,
|
|
388
|
+
tokens: data.tokens
|
|
389
|
+
})
|
|
390
|
+
];
|
|
391
|
+
const reasoning = state.reasoningParts.get(data.assistantMessageID);
|
|
392
|
+
if (reasoning !== void 0) {
|
|
393
|
+
attrs.push(attrString("ai.reasoning.text", reasoning));
|
|
394
|
+
}
|
|
395
|
+
traceShipper.endSpan(step.span, { attributes: attrs });
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
case "session.step.failed": {
|
|
399
|
+
const data = event.data;
|
|
400
|
+
const state = sessions.get(data.sessionID);
|
|
401
|
+
const step = state == null ? void 0 : state.stepSpans.get(data.assistantMessageID);
|
|
402
|
+
if (!state || !step) return;
|
|
403
|
+
state.stepSpans.delete(data.assistantMessageID);
|
|
404
|
+
traceShipper.endSpan(step.span, { error: errorString(data.error) });
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
case "session.text.ended": {
|
|
408
|
+
const data = event.data;
|
|
409
|
+
const state = sessions.get(data.sessionID);
|
|
410
|
+
if (!state) return;
|
|
411
|
+
const prev = (_b = state.outputParts.get(data.assistantMessageID)) != null ? _b : "";
|
|
412
|
+
state.outputParts.set(data.assistantMessageID, capText(prev + data.text));
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
case "session.reasoning.ended": {
|
|
416
|
+
const data = event.data;
|
|
417
|
+
const state = sessions.get(data.sessionID);
|
|
418
|
+
if (!state) return;
|
|
419
|
+
const prev = (_c = state.reasoningParts.get(data.assistantMessageID)) != null ? _c : "";
|
|
420
|
+
state.reasoningParts.set(data.assistantMessageID, capText(prev + data.text));
|
|
421
|
+
break;
|
|
422
|
+
}
|
|
423
|
+
case "session.tool.input.started": {
|
|
424
|
+
const data = event.data;
|
|
425
|
+
const state = sessions.get(data.sessionID);
|
|
426
|
+
if (!state) return;
|
|
427
|
+
state.toolNames.set(data.id, data.name);
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
430
|
+
case "session.tool.called": {
|
|
431
|
+
const data = event.data;
|
|
432
|
+
const state = sessions.get(data.sessionID);
|
|
433
|
+
if (!state || !state.eventId) return;
|
|
434
|
+
const parent = (_d = state.rootSpan) == null ? void 0 : _d.ids;
|
|
435
|
+
if (!parent) return;
|
|
436
|
+
const name = (_e = state.toolNames.get(data.id)) != null ? _e : "tool";
|
|
437
|
+
const span = traceShipper.startSpan({
|
|
438
|
+
name: "ai.toolCall",
|
|
439
|
+
parent,
|
|
440
|
+
eventId: state.eventId,
|
|
441
|
+
startTimeUnixNano: msToNs(event.created),
|
|
442
|
+
attributes: [
|
|
443
|
+
attrString("ai.operationId", "ai.toolCall"),
|
|
444
|
+
attrString("ai.toolCall.name", name),
|
|
445
|
+
attrString("ai.toolCall.id", data.id)
|
|
446
|
+
]
|
|
447
|
+
});
|
|
448
|
+
state.toolStarts.set(data.id, {
|
|
449
|
+
span,
|
|
450
|
+
name,
|
|
451
|
+
input: boundedStringify(data.input)
|
|
452
|
+
});
|
|
453
|
+
break;
|
|
454
|
+
}
|
|
455
|
+
case "session.tool.progress": {
|
|
456
|
+
const data = event.data;
|
|
457
|
+
const childSessionId = data.metadata["sessionID"];
|
|
458
|
+
if (typeof childSessionId !== "string") return;
|
|
459
|
+
const state = sessions.get(data.sessionID);
|
|
460
|
+
const start = state == null ? void 0 : state.toolStarts.get(data.id);
|
|
461
|
+
if (!(state == null ? void 0 : state.eventId) || !start) return;
|
|
462
|
+
subagentLinks.set(childSessionId, {
|
|
463
|
+
parentSessionId: data.sessionID,
|
|
464
|
+
toolSpan: start.span.ids,
|
|
465
|
+
eventId: state.eventId
|
|
466
|
+
});
|
|
467
|
+
break;
|
|
468
|
+
}
|
|
469
|
+
case "session.tool.success": {
|
|
470
|
+
const data = event.data;
|
|
471
|
+
const state = sessions.get(data.sessionID);
|
|
472
|
+
const start = state == null ? void 0 : state.toolStarts.get(data.id);
|
|
473
|
+
if (!state || !start) return;
|
|
474
|
+
state.toolStarts.delete(data.id);
|
|
475
|
+
traceShipper.endSpan(start.span, {
|
|
476
|
+
attributes: [
|
|
477
|
+
attrString("ai.toolCall.args", start.input),
|
|
478
|
+
attrString("ai.toolCall.result", boundedStringify(data.content))
|
|
479
|
+
]
|
|
480
|
+
});
|
|
481
|
+
break;
|
|
482
|
+
}
|
|
483
|
+
case "session.tool.failed": {
|
|
484
|
+
const data = event.data;
|
|
485
|
+
const state = sessions.get(data.sessionID);
|
|
486
|
+
const start = state == null ? void 0 : state.toolStarts.get(data.id);
|
|
487
|
+
if (!state || !start) return;
|
|
488
|
+
state.toolStarts.delete(data.id);
|
|
489
|
+
traceShipper.endSpan(start.span, {
|
|
490
|
+
attributes: [
|
|
491
|
+
attrString("ai.toolCall.args", start.input),
|
|
492
|
+
attrString("error.message", data.error.message),
|
|
493
|
+
...data.content !== void 0 ? [attrString("ai.toolCall.result", boundedStringify(data.content))] : []
|
|
494
|
+
],
|
|
495
|
+
error: errorString(data.error)
|
|
496
|
+
});
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
case "session.compaction.started": {
|
|
500
|
+
const data = event.data;
|
|
501
|
+
const state = sessions.get(data.sessionID);
|
|
502
|
+
if (!state || !state.eventId || !state.rootSpan) return;
|
|
503
|
+
state.compactionSpan = traceShipper.startSpan({
|
|
504
|
+
name: "ai.compaction",
|
|
505
|
+
parent: state.rootSpan.ids,
|
|
506
|
+
eventId: state.eventId,
|
|
507
|
+
attributes: [
|
|
508
|
+
attrString("ai.operationId", "ai.compaction"),
|
|
509
|
+
attrString("ai.compaction.reason", data.reason)
|
|
510
|
+
]
|
|
511
|
+
});
|
|
512
|
+
break;
|
|
513
|
+
}
|
|
514
|
+
case "session.compaction.ended": {
|
|
515
|
+
const data = event.data;
|
|
516
|
+
const state = sessions.get(data.sessionID);
|
|
517
|
+
if (!state || !state.compactionSpan) return;
|
|
518
|
+
const span = state.compactionSpan;
|
|
519
|
+
state.compactionSpan = void 0;
|
|
520
|
+
const attrs = [
|
|
521
|
+
attrString("ai.response.text", capText(data.text))
|
|
522
|
+
];
|
|
523
|
+
if (data.model && data.tokens) {
|
|
524
|
+
attrs.push(
|
|
525
|
+
...buildLlmGenAiAttributes({
|
|
526
|
+
providerID: data.model.providerID,
|
|
527
|
+
modelID: data.model.id,
|
|
528
|
+
tokens: data.tokens
|
|
529
|
+
})
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
if (data.cost !== void 0) {
|
|
533
|
+
attrs.push(attrString("ai.compaction.cost", String(data.cost)));
|
|
534
|
+
}
|
|
535
|
+
traceShipper.endSpan(span, { attributes: attrs });
|
|
536
|
+
break;
|
|
537
|
+
}
|
|
538
|
+
case "session.compaction.failed": {
|
|
539
|
+
const data = event.data;
|
|
540
|
+
const state = sessions.get(data.sessionID);
|
|
541
|
+
if (!state || !state.compactionSpan) return;
|
|
542
|
+
traceShipper.endSpan(state.compactionSpan, {
|
|
543
|
+
error: errorString(data.error)
|
|
544
|
+
});
|
|
545
|
+
state.compactionSpan = void 0;
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
548
|
+
case "session.execution.succeeded": {
|
|
549
|
+
const state = sessions.get(event.data.sessionID);
|
|
550
|
+
if (!state) return;
|
|
551
|
+
await finalizeTurn(state);
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
case "session.execution.interrupted": {
|
|
555
|
+
const state = sessions.get(event.data.sessionID);
|
|
556
|
+
if (!state) return;
|
|
557
|
+
await finalizeTurn(state);
|
|
558
|
+
break;
|
|
559
|
+
}
|
|
560
|
+
case "session.execution.failed": {
|
|
561
|
+
const state = sessions.get(event.data.sessionID);
|
|
562
|
+
if (!state) return;
|
|
563
|
+
await finalizeTurn(state, errorString(event.data.error));
|
|
564
|
+
break;
|
|
565
|
+
}
|
|
566
|
+
case "session.deleted": {
|
|
567
|
+
for (const [messageID, prompt] of admittedPrompts) {
|
|
568
|
+
if (prompt.sessionID === event.data.sessionID) admittedPrompts.delete(messageID);
|
|
569
|
+
}
|
|
570
|
+
subagentLinks.delete(event.data.sessionID);
|
|
571
|
+
const state = sessions.get(event.data.sessionID);
|
|
572
|
+
if (!state) return;
|
|
573
|
+
if (state.eventId || state.rootSpan) {
|
|
574
|
+
try {
|
|
575
|
+
await finalizeTurn(state);
|
|
576
|
+
} catch (e) {
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
sessions.delete(event.data.sessionID);
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// src/v2.ts
|
|
587
|
+
var RESUBSCRIBE_DELAY_MS = 1e3;
|
|
588
|
+
function appLog(level, message) {
|
|
589
|
+
console.log(`[raindrop-ai/opencode-plugin] [${level}] ${message}`);
|
|
590
|
+
}
|
|
591
|
+
function errorMessage(err) {
|
|
592
|
+
return err instanceof Error ? err.message : String(err);
|
|
593
|
+
}
|
|
594
|
+
function firstInstance(s) {
|
|
595
|
+
for (const instance of s.instances) return instance;
|
|
596
|
+
return void 0;
|
|
597
|
+
}
|
|
598
|
+
async function disposeAll(disposables) {
|
|
599
|
+
for (const disposable of [...disposables].reverse()) {
|
|
600
|
+
try {
|
|
601
|
+
await disposable.dispose();
|
|
602
|
+
} catch (e) {
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
disposables.length = 0;
|
|
606
|
+
}
|
|
607
|
+
function raindrop(opts = {}) {
|
|
608
|
+
let shared;
|
|
609
|
+
async function shutdownShared() {
|
|
610
|
+
const s = shared;
|
|
611
|
+
if (!s) return;
|
|
612
|
+
shared = void 0;
|
|
613
|
+
for (const instance of s.instances) await disposeAll(instance.disposables);
|
|
614
|
+
s.instances.clear();
|
|
615
|
+
s.abort.abort();
|
|
616
|
+
try {
|
|
617
|
+
await Promise.all([s.eventShipper.shutdown(), s.traceShipper.shutdown()]);
|
|
618
|
+
} finally {
|
|
619
|
+
try {
|
|
620
|
+
s.appGitContext.dispose();
|
|
621
|
+
} catch (e) {
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
async function removeInstance(s, instance) {
|
|
626
|
+
await disposeAll(instance.disposables);
|
|
627
|
+
s.instances.delete(instance);
|
|
628
|
+
if (shared === s && s.instances.size === 0) await shutdownShared();
|
|
629
|
+
}
|
|
630
|
+
async function runEventLoop(s) {
|
|
631
|
+
while (!s.abort.signal.aborted) {
|
|
632
|
+
const instance = firstInstance(s);
|
|
633
|
+
if (!instance) return;
|
|
634
|
+
try {
|
|
635
|
+
for await (const event of instance.ctx.event.subscribe({ signal: s.abort.signal })) {
|
|
636
|
+
try {
|
|
637
|
+
await s.runtime.onEvent(event);
|
|
638
|
+
} catch (err) {
|
|
639
|
+
rateLimitedErrorLog(
|
|
640
|
+
"event",
|
|
641
|
+
`[raindrop-ai/opencode-plugin] [error] Error in event dispatch: ${errorMessage(err)}`
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
} catch (err) {
|
|
646
|
+
if (s.abort.signal.aborted) return;
|
|
647
|
+
rateLimitedErrorLog(
|
|
648
|
+
"subscribe",
|
|
649
|
+
`[raindrop-ai/opencode-plugin] [error] Event subscription ended, resubscribing: ${errorMessage(err)}`
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
if (s.abort.signal.aborted) return;
|
|
653
|
+
await new Promise((resolve) => setTimeout(resolve, RESUBSCRIBE_DELAY_MS).unref());
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
function createShared(ctx) {
|
|
657
|
+
var _a, _b, _c;
|
|
658
|
+
const config = loadConfig((_a = opts.directory) != null ? _a : ctx.location.directory);
|
|
659
|
+
const resolvedLocalUrl = resolveLocalDebuggerBaseUrl(config.localWorkshopUrl);
|
|
660
|
+
const hasLocalDestination = resolvedLocalUrl !== null;
|
|
661
|
+
if (!config.writeKey && !hasLocalDestination) {
|
|
662
|
+
appLog(
|
|
663
|
+
"warn",
|
|
664
|
+
"RAINDROP_WRITE_KEY not set and no local Workshop daemon detected \u2014 Raindrop tracing disabled. Set RAINDROP_WRITE_KEY for cloud, or RAINDROP_LOCAL_WORKSHOP_URL / RAINDROP_LOCAL_DEBUGGER for local-only mode."
|
|
665
|
+
);
|
|
666
|
+
return void 0;
|
|
667
|
+
}
|
|
668
|
+
if (config.debug) {
|
|
669
|
+
const destinations = [
|
|
670
|
+
config.writeKey ? `cloud (${config.endpoint})` : null,
|
|
671
|
+
resolvedLocalUrl ? `local Workshop (${resolvedLocalUrl})` : null
|
|
672
|
+
].filter(Boolean);
|
|
673
|
+
appLog("info", `Raindrop tracing enabled \u2014 destinations: ${destinations.join(", ")}`);
|
|
674
|
+
}
|
|
675
|
+
const configuredAppGit = (_b = opts.appGit) != null ? _b : config.appGit;
|
|
676
|
+
const appGitContext = createAppGitContext(
|
|
677
|
+
((_c = config.writeKey) == null ? void 0 : _c.trim()) || hasLocalDestination ? configuredAppGit : false,
|
|
678
|
+
{ allowLocalGit: false }
|
|
679
|
+
);
|
|
680
|
+
const shipperOptions = {
|
|
681
|
+
writeKey: config.writeKey,
|
|
682
|
+
endpoint: config.endpoint,
|
|
683
|
+
debug: config.debug,
|
|
684
|
+
projectId: config.projectId,
|
|
685
|
+
localDebuggerUrl: config.localWorkshopUrl,
|
|
686
|
+
appGitContext
|
|
687
|
+
};
|
|
688
|
+
const eventShipper = new EventShipper(shipperOptions);
|
|
689
|
+
const traceShipper = new TraceShipper(shipperOptions);
|
|
690
|
+
const s = {
|
|
691
|
+
eventShipper,
|
|
692
|
+
traceShipper,
|
|
693
|
+
appGitContext,
|
|
694
|
+
abort: new AbortController(),
|
|
695
|
+
instances: /* @__PURE__ */ new Set(),
|
|
696
|
+
runtime: createV2Tracing(config, eventShipper, traceShipper, async (sessionID) => {
|
|
697
|
+
const instance = firstInstance(s);
|
|
698
|
+
if (!instance) return void 0;
|
|
699
|
+
const info = await instance.ctx.session.get({ sessionID });
|
|
700
|
+
return { parentID: info.parentID };
|
|
701
|
+
})
|
|
702
|
+
};
|
|
703
|
+
return s;
|
|
704
|
+
}
|
|
705
|
+
const handle = {
|
|
706
|
+
users: {
|
|
707
|
+
async identify(users) {
|
|
708
|
+
await (shared == null ? void 0 : shared.eventShipper.identify(users));
|
|
709
|
+
}
|
|
710
|
+
},
|
|
711
|
+
signals: {
|
|
712
|
+
async track(signal) {
|
|
713
|
+
await (shared == null ? void 0 : shared.eventShipper.trackSignal(signal));
|
|
714
|
+
}
|
|
715
|
+
},
|
|
716
|
+
currentEventId(sessionID) {
|
|
717
|
+
return shared == null ? void 0 : shared.runtime.currentEventId(sessionID);
|
|
718
|
+
},
|
|
719
|
+
async flush() {
|
|
720
|
+
const s = shared;
|
|
721
|
+
if (!s) return;
|
|
722
|
+
await Promise.all([s.eventShipper.flush(), s.traceShipper.flush()]);
|
|
723
|
+
},
|
|
724
|
+
async shutdown() {
|
|
725
|
+
await shutdownShared();
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
const plugin = Plugin.define({
|
|
729
|
+
id: "raindrop",
|
|
730
|
+
async setup(ctx) {
|
|
731
|
+
appLog("info", `Loading ${PLUGIN_NAME} v${PLUGIN_VERSION} (v2)`);
|
|
732
|
+
let s;
|
|
733
|
+
const instance = { ctx, disposables: [] };
|
|
734
|
+
try {
|
|
735
|
+
s = shared != null ? shared : createShared(ctx);
|
|
736
|
+
if (!s) return;
|
|
737
|
+
const isFirst = shared === void 0;
|
|
738
|
+
shared = s;
|
|
739
|
+
s.instances.add(instance);
|
|
740
|
+
const runtime = s.runtime;
|
|
741
|
+
const directory = ctx.location.directory;
|
|
742
|
+
instance.disposables.push(await ctx.session.hook("prompt", (e) => runtime.onPrompt(e, directory)));
|
|
743
|
+
instance.disposables.push(await ctx.session.hook("context", (e) => runtime.onContext(e)));
|
|
744
|
+
if (isFirst) void runEventLoop(s);
|
|
745
|
+
const owner = s;
|
|
746
|
+
return async () => {
|
|
747
|
+
await removeInstance(owner, instance);
|
|
748
|
+
};
|
|
749
|
+
} catch (err) {
|
|
750
|
+
rateLimitedErrorLog("setup", `[raindrop-ai/opencode-plugin] [error] Setup failed: ${errorMessage(err)}`);
|
|
751
|
+
if (s) await removeInstance(s, instance);
|
|
752
|
+
return () => {
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
return Object.assign(plugin, { raindrop: handle });
|
|
758
|
+
}
|
|
759
|
+
export {
|
|
760
|
+
raindrop as default
|
|
761
|
+
};
|