@raindrop-ai/opencode-plugin 0.0.19 → 0.1.1
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 +96 -16
- package/dist/index.d.cts +105 -3
- package/dist/index.d.ts +105 -3
- package/dist/index.js +96 -16
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -24,7 +24,7 @@ __export(index_exports, {
|
|
|
24
24
|
});
|
|
25
25
|
module.exports = __toCommonJS(index_exports);
|
|
26
26
|
|
|
27
|
-
// ../core/dist/chunk-
|
|
27
|
+
// ../core/dist/chunk-QTUH3BHZ.js
|
|
28
28
|
function getCrypto() {
|
|
29
29
|
const c = globalThis.crypto;
|
|
30
30
|
return c;
|
|
@@ -417,19 +417,52 @@ function projectIdHeaders(projectId) {
|
|
|
417
417
|
var SHUTDOWN_DEADLINE_MS = 1e4;
|
|
418
418
|
var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
419
419
|
function mergePatches(target, source) {
|
|
420
|
-
var _a, _b, _c, _d, _e, _f;
|
|
420
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
421
421
|
const out = { ...target, ...source };
|
|
422
422
|
if (target.properties || source.properties) {
|
|
423
423
|
out.properties = { ...(_a = target.properties) != null ? _a : {}, ...(_b = source.properties) != null ? _b : {} };
|
|
424
424
|
}
|
|
425
|
+
if (target.usage || source.usage) {
|
|
426
|
+
out.usage = { ...(_c = target.usage) != null ? _c : {} };
|
|
427
|
+
if (((_d = source.usage) == null ? void 0 : _d.promptTokens) !== void 0) {
|
|
428
|
+
out.usage.promptTokens = source.usage.promptTokens;
|
|
429
|
+
}
|
|
430
|
+
if (((_e = source.usage) == null ? void 0 : _e.completionTokens) !== void 0) {
|
|
431
|
+
out.usage.completionTokens = source.usage.completionTokens;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (source.error === void 0) {
|
|
435
|
+
out.error = target.error;
|
|
436
|
+
}
|
|
425
437
|
if (target.featureFlags || source.featureFlags) {
|
|
426
|
-
out.featureFlags = { ...(
|
|
438
|
+
out.featureFlags = { ...(_f = target.featureFlags) != null ? _f : {}, ...(_g = source.featureFlags) != null ? _g : {} };
|
|
427
439
|
}
|
|
428
440
|
if (target.attachments || source.attachments) {
|
|
429
|
-
out.attachments = [...(
|
|
441
|
+
out.attachments = [...(_h = target.attachments) != null ? _h : [], ...(_i = source.attachments) != null ? _i : []];
|
|
430
442
|
}
|
|
431
443
|
return out;
|
|
432
444
|
}
|
|
445
|
+
function isErrorLike(value) {
|
|
446
|
+
return value !== null && typeof value === "object";
|
|
447
|
+
}
|
|
448
|
+
function eventTelemetryProperties(patch) {
|
|
449
|
+
var _a, _b;
|
|
450
|
+
const properties = {};
|
|
451
|
+
if (((_a = patch.usage) == null ? void 0 : _a.promptTokens) !== void 0 && Number.isFinite(patch.usage.promptTokens)) {
|
|
452
|
+
properties["ai.usage.prompt_tokens"] = patch.usage.promptTokens;
|
|
453
|
+
}
|
|
454
|
+
if (((_b = patch.usage) == null ? void 0 : _b.completionTokens) !== void 0 && Number.isFinite(patch.usage.completionTokens)) {
|
|
455
|
+
properties["ai.usage.completion_tokens"] = patch.usage.completionTokens;
|
|
456
|
+
}
|
|
457
|
+
if (patch.error !== void 0 && patch.error !== null) {
|
|
458
|
+
const constructorName = patch.error instanceof Error && patch.error.constructor !== Error ? patch.error.constructor.name : void 0;
|
|
459
|
+
const errorType = patch.error instanceof Error ? patch.error.name && patch.error.name !== "Error" ? patch.error.name : constructorName || patch.error.name || "Error" : isErrorLike(patch.error) && typeof patch.error.name === "string" && patch.error.name.length > 0 ? patch.error.name : "Error";
|
|
460
|
+
const errorMessage = patch.error instanceof Error ? patch.error.message : isErrorLike(patch.error) && typeof patch.error.message === "string" ? patch.error.message : String(patch.error);
|
|
461
|
+
properties["error.type"] = errorType;
|
|
462
|
+
properties["error.message"] = errorMessage;
|
|
463
|
+
}
|
|
464
|
+
return properties;
|
|
465
|
+
}
|
|
433
466
|
var EventShipper = class {
|
|
434
467
|
constructor(opts) {
|
|
435
468
|
this.buffers = /* @__PURE__ */ new Map();
|
|
@@ -678,6 +711,11 @@ var EventShipper = class {
|
|
|
678
711
|
return;
|
|
679
712
|
}
|
|
680
713
|
const { wizardSession, ...restProperties } = (_e = accumulated.properties) != null ? _e : {};
|
|
714
|
+
let telemetryProperties = {};
|
|
715
|
+
try {
|
|
716
|
+
telemetryProperties = eventTelemetryProperties(accumulated);
|
|
717
|
+
} catch (e) {
|
|
718
|
+
}
|
|
681
719
|
const convoId = (_f = accumulated.convoId) != null ? _f : sticky.convoId;
|
|
682
720
|
const isPending = (_h = (_g = accumulated.isPending) != null ? _g : sticky.isPending) != null ? _h : true;
|
|
683
721
|
const payload = {
|
|
@@ -687,12 +725,13 @@ var EventShipper = class {
|
|
|
687
725
|
timestamp: (_i = accumulated.timestamp) != null ? _i : (/* @__PURE__ */ new Date()).toISOString(),
|
|
688
726
|
ai_data: {
|
|
689
727
|
input: accumulated.input,
|
|
690
|
-
output: accumulated.output,
|
|
728
|
+
output: accumulated.error !== void 0 && accumulated.error !== null ? void 0 : accumulated.output,
|
|
691
729
|
model: accumulated.model,
|
|
692
730
|
convo_id: convoId
|
|
693
731
|
},
|
|
694
732
|
properties: {
|
|
695
733
|
...restProperties,
|
|
734
|
+
...telemetryProperties,
|
|
696
735
|
...wizardSession ? { "raindrop.wizardSession": wizardSession } : {},
|
|
697
736
|
$context: this.context
|
|
698
737
|
},
|
|
@@ -850,6 +889,13 @@ function redactJsonAttributeValue(key, value) {
|
|
|
850
889
|
if (scrubbedJson === json) return void 0;
|
|
851
890
|
return { stringValue: scrubbedJson };
|
|
852
891
|
}
|
|
892
|
+
function identityAttrs(args) {
|
|
893
|
+
return [
|
|
894
|
+
attrString("ai.telemetry.metadata.raindrop.ai.userId", args.userId),
|
|
895
|
+
attrString("ai.telemetry.metadata.raindrop.convoId", args.convoId),
|
|
896
|
+
attrString("ai.telemetry.metadata.raindrop.eventName", args.eventName)
|
|
897
|
+
];
|
|
898
|
+
}
|
|
853
899
|
function applyOtelSpanAttributeLimit(limit) {
|
|
854
900
|
var _a, _b;
|
|
855
901
|
try {
|
|
@@ -974,6 +1020,7 @@ var TraceShipper = class {
|
|
|
974
1020
|
const started = (_a = args.startTimeUnixNano) != null ? _a : nowUnixNanoString();
|
|
975
1021
|
const attrs = [
|
|
976
1022
|
attrString("ai.telemetry.metadata.raindrop.eventId", args.eventId),
|
|
1023
|
+
...identityAttrs(args),
|
|
977
1024
|
attrString("ai.operationId", args.operationId)
|
|
978
1025
|
];
|
|
979
1026
|
if ((_b = args.attributes) == null ? void 0 : _b.length) attrs.push(...args.attributes);
|
|
@@ -1029,7 +1076,8 @@ var TraceShipper = class {
|
|
|
1029
1076
|
var _a;
|
|
1030
1077
|
const ids = createSpanIds(args.parent);
|
|
1031
1078
|
const attrs = [
|
|
1032
|
-
attrString("ai.telemetry.metadata.raindrop.eventId", args.eventId)
|
|
1079
|
+
attrString("ai.telemetry.metadata.raindrop.eventId", args.eventId),
|
|
1080
|
+
...identityAttrs(args)
|
|
1033
1081
|
];
|
|
1034
1082
|
if ((_a = args.attributes) == null ? void 0 : _a.length) attrs.push(...args.attributes);
|
|
1035
1083
|
const otlp = buildOtlpSpan({
|
|
@@ -1248,7 +1296,7 @@ function resolveLocalWorkshopUrl(fileValue) {
|
|
|
1248
1296
|
// package.json
|
|
1249
1297
|
var package_default = {
|
|
1250
1298
|
name: "@raindrop-ai/opencode-plugin",
|
|
1251
|
-
version: "0.
|
|
1299
|
+
version: "0.1.1",
|
|
1252
1300
|
description: "Raindrop observability plugin for OpenCode \u2014 automatic session/event/span tracing",
|
|
1253
1301
|
type: "module",
|
|
1254
1302
|
main: "dist/index.js",
|
|
@@ -1528,6 +1576,8 @@ function createSessionState(sessionId) {
|
|
|
1528
1576
|
return {
|
|
1529
1577
|
sessionId,
|
|
1530
1578
|
currentInput: "",
|
|
1579
|
+
turnOutputParts: [],
|
|
1580
|
+
turnOutputTokens: 0,
|
|
1531
1581
|
outputParts: /* @__PURE__ */ new Map(),
|
|
1532
1582
|
reasoningParts: /* @__PURE__ */ new Map(),
|
|
1533
1583
|
toolSpanStarts: /* @__PURE__ */ new Map(),
|
|
@@ -1633,7 +1683,7 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
|
|
|
1633
1683
|
// event — session lifecycle + streaming parts
|
|
1634
1684
|
// ------------------------------------------------------------------
|
|
1635
1685
|
event: async ({ event }) => {
|
|
1636
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
1686
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
1637
1687
|
try {
|
|
1638
1688
|
const props = event.properties;
|
|
1639
1689
|
const info = props["info"];
|
|
@@ -1758,6 +1808,10 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
|
|
|
1758
1808
|
const outputText = state.outputParts.get(messageId);
|
|
1759
1809
|
const reasoningText = state.reasoningParts.get(messageId);
|
|
1760
1810
|
const isToolCallsCompletion = finishReason === "tool-calls";
|
|
1811
|
+
if (outputText !== void 0 && outputText.trim().length > 0) {
|
|
1812
|
+
state.turnOutputParts.push(outputText);
|
|
1813
|
+
}
|
|
1814
|
+
state.turnOutputTokens += outputTokens;
|
|
1761
1815
|
const hasIntermediateReasoning = reasoningText !== void 0 && reasoningText.trim().length > 0;
|
|
1762
1816
|
if (outputText === void 0 && !(isToolCallsCompletion && hasIntermediateReasoning))
|
|
1763
1817
|
return;
|
|
@@ -1812,6 +1866,7 @@ type: ${name != null ? name : "UnknownError"}`;
|
|
|
1812
1866
|
return;
|
|
1813
1867
|
}
|
|
1814
1868
|
if (outputText === void 0) return;
|
|
1869
|
+
const turnOutputText = capText2(state.turnOutputParts.join("\n\n"));
|
|
1815
1870
|
const rootSpan = state.currentRootSpan;
|
|
1816
1871
|
const isCompaction = state.isCompacting === true;
|
|
1817
1872
|
if (state.parentId) {
|
|
@@ -1823,7 +1878,7 @@ type: ${name != null ? name : "UnknownError"}`;
|
|
|
1823
1878
|
"ai.prompt.messages",
|
|
1824
1879
|
buildPromptMessages(state.currentSystemPrompt, state.currentInput)
|
|
1825
1880
|
),
|
|
1826
|
-
attrString("ai.response.text",
|
|
1881
|
+
attrString("ai.response.text", turnOutputText),
|
|
1827
1882
|
attrString("gen_ai.system", providerID),
|
|
1828
1883
|
attrString("gen_ai.request.model", modelID),
|
|
1829
1884
|
attrString("gen_ai.response.model", modelID),
|
|
@@ -1875,7 +1930,7 @@ type: ${name != null ? name : "UnknownError"}`;
|
|
|
1875
1930
|
"ai.prompt.messages",
|
|
1876
1931
|
buildPromptMessages(state.currentSystemPrompt, state.currentInput)
|
|
1877
1932
|
),
|
|
1878
|
-
attrString("ai.response.text",
|
|
1933
|
+
attrString("ai.response.text", turnOutputText),
|
|
1879
1934
|
attrString("gen_ai.system", providerID),
|
|
1880
1935
|
attrString("gen_ai.request.model", modelID),
|
|
1881
1936
|
attrString("gen_ai.response.model", modelID),
|
|
@@ -1904,8 +1959,8 @@ type: ${name != null ? name : "UnknownError"}`;
|
|
|
1904
1959
|
}
|
|
1905
1960
|
}
|
|
1906
1961
|
state.currentRootSpan = void 0;
|
|
1907
|
-
const hasAssistantResponse =
|
|
1908
|
-
if (!hasAssistantResponse) {
|
|
1962
|
+
const hasAssistantResponse = turnOutputText.trim().length > 0;
|
|
1963
|
+
if (!hasAssistantResponse && !msgError) {
|
|
1909
1964
|
await traceShipper.flush();
|
|
1910
1965
|
state.currentEventId = void 0;
|
|
1911
1966
|
return;
|
|
@@ -1914,7 +1969,15 @@ type: ${name != null ? name : "UnknownError"}`;
|
|
|
1914
1969
|
await eventShipper.finish(state.currentEventId, {
|
|
1915
1970
|
userId: (_d = (_c = state.eventMetadata) == null ? void 0 : _c.userId) != null ? _d : state.sessionId,
|
|
1916
1971
|
model: modelID,
|
|
1917
|
-
output:
|
|
1972
|
+
output: msgError ? void 0 : turnOutputText,
|
|
1973
|
+
usage: {
|
|
1974
|
+
promptTokens: inputTokens,
|
|
1975
|
+
completionTokens: state.turnOutputTokens
|
|
1976
|
+
},
|
|
1977
|
+
error: msgError ? {
|
|
1978
|
+
name: msgError.name,
|
|
1979
|
+
message: (_g = (_f = (_e = msgError.data) == null ? void 0 : _e.message) != null ? _f : msgError.name) != null ? _g : "OpenCode request failed"
|
|
1980
|
+
} : void 0,
|
|
1918
1981
|
properties: {
|
|
1919
1982
|
plugin_version: PLUGIN_VERSION,
|
|
1920
1983
|
message_id: messageId,
|
|
@@ -1958,7 +2021,7 @@ type: ${name != null ? name : "UnknownError"}`;
|
|
|
1958
2021
|
await Promise.all([eventShipper.flush(), traceShipper.flush()]);
|
|
1959
2022
|
sessions.delete(String(sessionID));
|
|
1960
2023
|
} else if (event.type === "session.error") {
|
|
1961
|
-
const errorSessionID = (
|
|
2024
|
+
const errorSessionID = (_h = props["sessionID"]) != null ? _h : sessionID;
|
|
1962
2025
|
if (errorSessionID === void 0 || errorSessionID === "")
|
|
1963
2026
|
throw new Error("session.error: missing sessionID");
|
|
1964
2027
|
const state = sessions.get(String(errorSessionID));
|
|
@@ -1966,7 +2029,7 @@ type: ${name != null ? name : "UnknownError"}`;
|
|
|
1966
2029
|
const errorObj = props["error"];
|
|
1967
2030
|
if (errorObj == null) throw new Error("session.error: missing error object");
|
|
1968
2031
|
const errorName = errorObj.name;
|
|
1969
|
-
const errorMessage = (
|
|
2032
|
+
const errorMessage = (_j = (_i = errorObj.data) == null ? void 0 : _i.message) != null ? _j : errorObj.name;
|
|
1970
2033
|
if (errorName == null && errorMessage == null)
|
|
1971
2034
|
throw new Error("session.error: error object must have name or data.message");
|
|
1972
2035
|
const errorStr = `${errorMessage != null ? errorMessage : errorName}
|
|
@@ -2043,6 +2106,8 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
2043
2106
|
};
|
|
2044
2107
|
const userText = capText2(textParts.join("\n"));
|
|
2045
2108
|
state.currentInput = userText;
|
|
2109
|
+
state.turnOutputParts = [];
|
|
2110
|
+
state.turnOutputTokens = 0;
|
|
2046
2111
|
if (state.parentId) {
|
|
2047
2112
|
if (!state.parentTaskSpanIds || !state.parentEventContext) {
|
|
2048
2113
|
return;
|
|
@@ -2260,6 +2325,21 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
2260
2325
|
}
|
|
2261
2326
|
|
|
2262
2327
|
// src/index.ts
|
|
2328
|
+
function createRaindropHandle(eventShipper, traceShipper) {
|
|
2329
|
+
return {
|
|
2330
|
+
users: {
|
|
2331
|
+
async identify(users) {
|
|
2332
|
+
await eventShipper.identify(users);
|
|
2333
|
+
}
|
|
2334
|
+
},
|
|
2335
|
+
async flush() {
|
|
2336
|
+
await Promise.all([eventShipper.flush(), traceShipper.flush()]);
|
|
2337
|
+
},
|
|
2338
|
+
async shutdown() {
|
|
2339
|
+
await Promise.all([eventShipper.shutdown(), traceShipper.shutdown()]);
|
|
2340
|
+
}
|
|
2341
|
+
};
|
|
2342
|
+
}
|
|
2263
2343
|
async function plugin(input) {
|
|
2264
2344
|
var _a;
|
|
2265
2345
|
const config = loadConfig(input.directory);
|
|
@@ -2305,5 +2385,5 @@ async function plugin(input) {
|
|
|
2305
2385
|
eventShipper,
|
|
2306
2386
|
traceShipper
|
|
2307
2387
|
);
|
|
2308
|
-
return hooks;
|
|
2388
|
+
return { ...hooks, raindrop: createRaindropHandle(eventShipper, traceShipper) };
|
|
2309
2389
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,107 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Hooks, PluginInput } from '@opencode-ai/plugin';
|
|
2
2
|
|
|
3
|
+
type IdentifyInput = {
|
|
4
|
+
userId: string;
|
|
5
|
+
traits?: Record<string, unknown>;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Run telemetry egress with OpenTelemetry tracing suppressed.
|
|
10
|
+
*
|
|
11
|
+
* Why this exists
|
|
12
|
+
* ---------------
|
|
13
|
+
* Raindrop integrations ship spans/events over HTTP with the global `fetch`
|
|
14
|
+
* (see {@link ../http.ts `postJson`}). When the host app also runs an OTel
|
|
15
|
+
* fetch/undici instrumentation — e.g. `@vercel/otel`'s `registerOTel`, which
|
|
16
|
+
* every Eve agent installs — that instrumentation wraps *our own* telemetry
|
|
17
|
+
* POSTs in a `fetch POST <endpoint>` span. Those spans are then handed to the
|
|
18
|
+
* very exporter that issued the request, so they get shipped right back to
|
|
19
|
+
* Raindrop and Workshop as standalone "runs" (and, because each export issues
|
|
20
|
+
* another fetch, they feed back on themselves). The result is a run list
|
|
21
|
+
* flooded with `fetch POST .../v1/traces`, `.../events/track_partial` and
|
|
22
|
+
* `.../live` entries that drown out the real agent turns — especially with
|
|
23
|
+
* sub-agents, where every sandbox runs its own instrumentation.
|
|
24
|
+
*
|
|
25
|
+
* The OTel-blessed fix is to mark the active context as "tracing suppressed"
|
|
26
|
+
* around the request; instrumentations check `isTracingSuppressed` and return
|
|
27
|
+
* a no-op span instead of recording one. We do this through a hook stashed on
|
|
28
|
+
* `globalThis` by the Node entrypoint ({@link ../index.node.ts}) so that:
|
|
29
|
+
* - `@opentelemetry/api` / `@opentelemetry/core` stay *optional* — core never
|
|
30
|
+
* hard-depends on them, and the hook is simply absent when they (and thus
|
|
31
|
+
* any instrumentation to suppress) are not installed; and
|
|
32
|
+
* - the browser bundle never pulls in `node:module`, mirroring how core
|
|
33
|
+
* injects `AsyncLocalStorage` via `RAINDROP_ASYNC_LOCAL_STORAGE`.
|
|
34
|
+
*
|
|
35
|
+
* When no hook is present the callback runs unchanged, so suppression is a
|
|
36
|
+
* best-effort no-op rather than a hard requirement.
|
|
37
|
+
*/
|
|
38
|
+
/** Hook signature: run `fn` with OTel tracing suppressed, returning its value. */
|
|
39
|
+
type SuppressTracingHook = <T>(fn: () => T) => T;
|
|
40
|
+
declare global {
|
|
41
|
+
var RAINDROP_SUPPRESS_TRACING: SuppressTracingHook | undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type ParentSpanContext = {
|
|
45
|
+
traceIdB64: string;
|
|
46
|
+
spanIdB64: string;
|
|
47
|
+
eventId: string;
|
|
48
|
+
};
|
|
49
|
+
interface ContextSpan {
|
|
50
|
+
readonly traceIdB64: string;
|
|
51
|
+
readonly spanIdB64: string;
|
|
52
|
+
readonly eventId: string;
|
|
53
|
+
log?(data: Record<string, unknown>): void;
|
|
54
|
+
}
|
|
55
|
+
interface AsyncLocalStorageLike<T> {
|
|
56
|
+
getStore(): T | undefined;
|
|
57
|
+
run<R>(store: T, callback: () => R): R;
|
|
58
|
+
enterWith?(store: T): void;
|
|
59
|
+
}
|
|
60
|
+
declare abstract class ContextManager {
|
|
61
|
+
abstract getParentSpanIds(): ParentSpanContext | undefined;
|
|
62
|
+
abstract runInContext<R>(span: ContextSpan, callback: () => R): R;
|
|
63
|
+
abstract getCurrentSpan(): ContextSpan | undefined;
|
|
64
|
+
abstract isReady(): boolean;
|
|
65
|
+
}
|
|
66
|
+
declare global {
|
|
67
|
+
var RAINDROP_CONTEXT_MANAGER: (new () => ContextManager) | undefined;
|
|
68
|
+
var RAINDROP_ASYNC_LOCAL_STORAGE: (new <T>() => AsyncLocalStorageLike<T>) | undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The Raindrop control handle exposed alongside the OpenCode hooks.
|
|
73
|
+
*
|
|
74
|
+
* The plugin's public surface is the framework `Hooks` object, which OpenCode
|
|
75
|
+
* consumes and whose keyspace it owns. Rather than pollute that keyspace with a
|
|
76
|
+
* top-level `users` (as the client-factory wrappers do), the identify surface
|
|
77
|
+
* lives under a namespaced `raindrop` handle — mirroring `@raindrop-ai/eve`'s
|
|
78
|
+
* `raindrop` handle precedent — so application/tool code can attach user traits
|
|
79
|
+
* without waiting for a session hook to fire.
|
|
80
|
+
*/
|
|
81
|
+
interface RaindropOpencodeHandle {
|
|
82
|
+
users: {
|
|
83
|
+
/**
|
|
84
|
+
* Attach traits to one or more users (posts to `users/identify`). Buffered
|
|
85
|
+
* in the shared EventShipper and delivered on the next {@link flush} / span
|
|
86
|
+
* flush, so pair a bare identify with `flush()` when there is no active
|
|
87
|
+
* OpenCode session driving delivery.
|
|
88
|
+
*/
|
|
89
|
+
identify(users: IdentifyInput | IdentifyInput[]): Promise<void>;
|
|
90
|
+
};
|
|
91
|
+
/** Drain the buffered event + trace shippers. */
|
|
92
|
+
flush(): Promise<void>;
|
|
93
|
+
/** Flush and tear down the shippers (also wired into session teardown). */
|
|
94
|
+
shutdown(): Promise<void>;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The plugin's return value: the OpenCode {@link Hooks} plus the additive
|
|
98
|
+
* {@link RaindropOpencodeHandle}. `raindrop` is optional because the plugin
|
|
99
|
+
* returns bare `{}` (no handle) when tracing is disabled — no write key and no
|
|
100
|
+
* local Workshop destination.
|
|
101
|
+
*/
|
|
102
|
+
type RaindropOpencodeHooks = Hooks & {
|
|
103
|
+
raindrop?: RaindropOpencodeHandle;
|
|
104
|
+
};
|
|
3
105
|
/**
|
|
4
106
|
* Raindrop plugin for OpenCode.
|
|
5
107
|
*
|
|
@@ -12,6 +114,6 @@ import { PluginInput, Hooks } from '@opencode-ai/plugin';
|
|
|
12
114
|
*
|
|
13
115
|
* Optional config file: ~/.config/opencode/raindrop.json or .opencode/raindrop.json
|
|
14
116
|
*/
|
|
15
|
-
declare function plugin(input: PluginInput): Promise<
|
|
117
|
+
declare function plugin(input: PluginInput): Promise<RaindropOpencodeHooks>;
|
|
16
118
|
|
|
17
|
-
export { plugin as default };
|
|
119
|
+
export { type IdentifyInput, type RaindropOpencodeHandle, type RaindropOpencodeHooks, plugin as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,107 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Hooks, PluginInput } from '@opencode-ai/plugin';
|
|
2
2
|
|
|
3
|
+
type IdentifyInput = {
|
|
4
|
+
userId: string;
|
|
5
|
+
traits?: Record<string, unknown>;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Run telemetry egress with OpenTelemetry tracing suppressed.
|
|
10
|
+
*
|
|
11
|
+
* Why this exists
|
|
12
|
+
* ---------------
|
|
13
|
+
* Raindrop integrations ship spans/events over HTTP with the global `fetch`
|
|
14
|
+
* (see {@link ../http.ts `postJson`}). When the host app also runs an OTel
|
|
15
|
+
* fetch/undici instrumentation — e.g. `@vercel/otel`'s `registerOTel`, which
|
|
16
|
+
* every Eve agent installs — that instrumentation wraps *our own* telemetry
|
|
17
|
+
* POSTs in a `fetch POST <endpoint>` span. Those spans are then handed to the
|
|
18
|
+
* very exporter that issued the request, so they get shipped right back to
|
|
19
|
+
* Raindrop and Workshop as standalone "runs" (and, because each export issues
|
|
20
|
+
* another fetch, they feed back on themselves). The result is a run list
|
|
21
|
+
* flooded with `fetch POST .../v1/traces`, `.../events/track_partial` and
|
|
22
|
+
* `.../live` entries that drown out the real agent turns — especially with
|
|
23
|
+
* sub-agents, where every sandbox runs its own instrumentation.
|
|
24
|
+
*
|
|
25
|
+
* The OTel-blessed fix is to mark the active context as "tracing suppressed"
|
|
26
|
+
* around the request; instrumentations check `isTracingSuppressed` and return
|
|
27
|
+
* a no-op span instead of recording one. We do this through a hook stashed on
|
|
28
|
+
* `globalThis` by the Node entrypoint ({@link ../index.node.ts}) so that:
|
|
29
|
+
* - `@opentelemetry/api` / `@opentelemetry/core` stay *optional* — core never
|
|
30
|
+
* hard-depends on them, and the hook is simply absent when they (and thus
|
|
31
|
+
* any instrumentation to suppress) are not installed; and
|
|
32
|
+
* - the browser bundle never pulls in `node:module`, mirroring how core
|
|
33
|
+
* injects `AsyncLocalStorage` via `RAINDROP_ASYNC_LOCAL_STORAGE`.
|
|
34
|
+
*
|
|
35
|
+
* When no hook is present the callback runs unchanged, so suppression is a
|
|
36
|
+
* best-effort no-op rather than a hard requirement.
|
|
37
|
+
*/
|
|
38
|
+
/** Hook signature: run `fn` with OTel tracing suppressed, returning its value. */
|
|
39
|
+
type SuppressTracingHook = <T>(fn: () => T) => T;
|
|
40
|
+
declare global {
|
|
41
|
+
var RAINDROP_SUPPRESS_TRACING: SuppressTracingHook | undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type ParentSpanContext = {
|
|
45
|
+
traceIdB64: string;
|
|
46
|
+
spanIdB64: string;
|
|
47
|
+
eventId: string;
|
|
48
|
+
};
|
|
49
|
+
interface ContextSpan {
|
|
50
|
+
readonly traceIdB64: string;
|
|
51
|
+
readonly spanIdB64: string;
|
|
52
|
+
readonly eventId: string;
|
|
53
|
+
log?(data: Record<string, unknown>): void;
|
|
54
|
+
}
|
|
55
|
+
interface AsyncLocalStorageLike<T> {
|
|
56
|
+
getStore(): T | undefined;
|
|
57
|
+
run<R>(store: T, callback: () => R): R;
|
|
58
|
+
enterWith?(store: T): void;
|
|
59
|
+
}
|
|
60
|
+
declare abstract class ContextManager {
|
|
61
|
+
abstract getParentSpanIds(): ParentSpanContext | undefined;
|
|
62
|
+
abstract runInContext<R>(span: ContextSpan, callback: () => R): R;
|
|
63
|
+
abstract getCurrentSpan(): ContextSpan | undefined;
|
|
64
|
+
abstract isReady(): boolean;
|
|
65
|
+
}
|
|
66
|
+
declare global {
|
|
67
|
+
var RAINDROP_CONTEXT_MANAGER: (new () => ContextManager) | undefined;
|
|
68
|
+
var RAINDROP_ASYNC_LOCAL_STORAGE: (new <T>() => AsyncLocalStorageLike<T>) | undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The Raindrop control handle exposed alongside the OpenCode hooks.
|
|
73
|
+
*
|
|
74
|
+
* The plugin's public surface is the framework `Hooks` object, which OpenCode
|
|
75
|
+
* consumes and whose keyspace it owns. Rather than pollute that keyspace with a
|
|
76
|
+
* top-level `users` (as the client-factory wrappers do), the identify surface
|
|
77
|
+
* lives under a namespaced `raindrop` handle — mirroring `@raindrop-ai/eve`'s
|
|
78
|
+
* `raindrop` handle precedent — so application/tool code can attach user traits
|
|
79
|
+
* without waiting for a session hook to fire.
|
|
80
|
+
*/
|
|
81
|
+
interface RaindropOpencodeHandle {
|
|
82
|
+
users: {
|
|
83
|
+
/**
|
|
84
|
+
* Attach traits to one or more users (posts to `users/identify`). Buffered
|
|
85
|
+
* in the shared EventShipper and delivered on the next {@link flush} / span
|
|
86
|
+
* flush, so pair a bare identify with `flush()` when there is no active
|
|
87
|
+
* OpenCode session driving delivery.
|
|
88
|
+
*/
|
|
89
|
+
identify(users: IdentifyInput | IdentifyInput[]): Promise<void>;
|
|
90
|
+
};
|
|
91
|
+
/** Drain the buffered event + trace shippers. */
|
|
92
|
+
flush(): Promise<void>;
|
|
93
|
+
/** Flush and tear down the shippers (also wired into session teardown). */
|
|
94
|
+
shutdown(): Promise<void>;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The plugin's return value: the OpenCode {@link Hooks} plus the additive
|
|
98
|
+
* {@link RaindropOpencodeHandle}. `raindrop` is optional because the plugin
|
|
99
|
+
* returns bare `{}` (no handle) when tracing is disabled — no write key and no
|
|
100
|
+
* local Workshop destination.
|
|
101
|
+
*/
|
|
102
|
+
type RaindropOpencodeHooks = Hooks & {
|
|
103
|
+
raindrop?: RaindropOpencodeHandle;
|
|
104
|
+
};
|
|
3
105
|
/**
|
|
4
106
|
* Raindrop plugin for OpenCode.
|
|
5
107
|
*
|
|
@@ -12,6 +114,6 @@ import { PluginInput, Hooks } from '@opencode-ai/plugin';
|
|
|
12
114
|
*
|
|
13
115
|
* Optional config file: ~/.config/opencode/raindrop.json or .opencode/raindrop.json
|
|
14
116
|
*/
|
|
15
|
-
declare function plugin(input: PluginInput): Promise<
|
|
117
|
+
declare function plugin(input: PluginInput): Promise<RaindropOpencodeHooks>;
|
|
16
118
|
|
|
17
|
-
export { plugin as default };
|
|
119
|
+
export { type IdentifyInput, type RaindropOpencodeHandle, type RaindropOpencodeHooks, plugin as default };
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
5
5
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
6
|
});
|
|
7
7
|
|
|
8
|
-
// ../core/dist/chunk-
|
|
8
|
+
// ../core/dist/chunk-QTUH3BHZ.js
|
|
9
9
|
function getCrypto() {
|
|
10
10
|
const c = globalThis.crypto;
|
|
11
11
|
return c;
|
|
@@ -398,19 +398,52 @@ function projectIdHeaders(projectId) {
|
|
|
398
398
|
var SHUTDOWN_DEADLINE_MS = 1e4;
|
|
399
399
|
var POST_SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
400
400
|
function mergePatches(target, source) {
|
|
401
|
-
var _a, _b, _c, _d, _e, _f;
|
|
401
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
402
402
|
const out = { ...target, ...source };
|
|
403
403
|
if (target.properties || source.properties) {
|
|
404
404
|
out.properties = { ...(_a = target.properties) != null ? _a : {}, ...(_b = source.properties) != null ? _b : {} };
|
|
405
405
|
}
|
|
406
|
+
if (target.usage || source.usage) {
|
|
407
|
+
out.usage = { ...(_c = target.usage) != null ? _c : {} };
|
|
408
|
+
if (((_d = source.usage) == null ? void 0 : _d.promptTokens) !== void 0) {
|
|
409
|
+
out.usage.promptTokens = source.usage.promptTokens;
|
|
410
|
+
}
|
|
411
|
+
if (((_e = source.usage) == null ? void 0 : _e.completionTokens) !== void 0) {
|
|
412
|
+
out.usage.completionTokens = source.usage.completionTokens;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
if (source.error === void 0) {
|
|
416
|
+
out.error = target.error;
|
|
417
|
+
}
|
|
406
418
|
if (target.featureFlags || source.featureFlags) {
|
|
407
|
-
out.featureFlags = { ...(
|
|
419
|
+
out.featureFlags = { ...(_f = target.featureFlags) != null ? _f : {}, ...(_g = source.featureFlags) != null ? _g : {} };
|
|
408
420
|
}
|
|
409
421
|
if (target.attachments || source.attachments) {
|
|
410
|
-
out.attachments = [...(
|
|
422
|
+
out.attachments = [...(_h = target.attachments) != null ? _h : [], ...(_i = source.attachments) != null ? _i : []];
|
|
411
423
|
}
|
|
412
424
|
return out;
|
|
413
425
|
}
|
|
426
|
+
function isErrorLike(value) {
|
|
427
|
+
return value !== null && typeof value === "object";
|
|
428
|
+
}
|
|
429
|
+
function eventTelemetryProperties(patch) {
|
|
430
|
+
var _a, _b;
|
|
431
|
+
const properties = {};
|
|
432
|
+
if (((_a = patch.usage) == null ? void 0 : _a.promptTokens) !== void 0 && Number.isFinite(patch.usage.promptTokens)) {
|
|
433
|
+
properties["ai.usage.prompt_tokens"] = patch.usage.promptTokens;
|
|
434
|
+
}
|
|
435
|
+
if (((_b = patch.usage) == null ? void 0 : _b.completionTokens) !== void 0 && Number.isFinite(patch.usage.completionTokens)) {
|
|
436
|
+
properties["ai.usage.completion_tokens"] = patch.usage.completionTokens;
|
|
437
|
+
}
|
|
438
|
+
if (patch.error !== void 0 && patch.error !== null) {
|
|
439
|
+
const constructorName = patch.error instanceof Error && patch.error.constructor !== Error ? patch.error.constructor.name : void 0;
|
|
440
|
+
const errorType = patch.error instanceof Error ? patch.error.name && patch.error.name !== "Error" ? patch.error.name : constructorName || patch.error.name || "Error" : isErrorLike(patch.error) && typeof patch.error.name === "string" && patch.error.name.length > 0 ? patch.error.name : "Error";
|
|
441
|
+
const errorMessage = patch.error instanceof Error ? patch.error.message : isErrorLike(patch.error) && typeof patch.error.message === "string" ? patch.error.message : String(patch.error);
|
|
442
|
+
properties["error.type"] = errorType;
|
|
443
|
+
properties["error.message"] = errorMessage;
|
|
444
|
+
}
|
|
445
|
+
return properties;
|
|
446
|
+
}
|
|
414
447
|
var EventShipper = class {
|
|
415
448
|
constructor(opts) {
|
|
416
449
|
this.buffers = /* @__PURE__ */ new Map();
|
|
@@ -659,6 +692,11 @@ var EventShipper = class {
|
|
|
659
692
|
return;
|
|
660
693
|
}
|
|
661
694
|
const { wizardSession, ...restProperties } = (_e = accumulated.properties) != null ? _e : {};
|
|
695
|
+
let telemetryProperties = {};
|
|
696
|
+
try {
|
|
697
|
+
telemetryProperties = eventTelemetryProperties(accumulated);
|
|
698
|
+
} catch (e) {
|
|
699
|
+
}
|
|
662
700
|
const convoId = (_f = accumulated.convoId) != null ? _f : sticky.convoId;
|
|
663
701
|
const isPending = (_h = (_g = accumulated.isPending) != null ? _g : sticky.isPending) != null ? _h : true;
|
|
664
702
|
const payload = {
|
|
@@ -668,12 +706,13 @@ var EventShipper = class {
|
|
|
668
706
|
timestamp: (_i = accumulated.timestamp) != null ? _i : (/* @__PURE__ */ new Date()).toISOString(),
|
|
669
707
|
ai_data: {
|
|
670
708
|
input: accumulated.input,
|
|
671
|
-
output: accumulated.output,
|
|
709
|
+
output: accumulated.error !== void 0 && accumulated.error !== null ? void 0 : accumulated.output,
|
|
672
710
|
model: accumulated.model,
|
|
673
711
|
convo_id: convoId
|
|
674
712
|
},
|
|
675
713
|
properties: {
|
|
676
714
|
...restProperties,
|
|
715
|
+
...telemetryProperties,
|
|
677
716
|
...wizardSession ? { "raindrop.wizardSession": wizardSession } : {},
|
|
678
717
|
$context: this.context
|
|
679
718
|
},
|
|
@@ -831,6 +870,13 @@ function redactJsonAttributeValue(key, value) {
|
|
|
831
870
|
if (scrubbedJson === json) return void 0;
|
|
832
871
|
return { stringValue: scrubbedJson };
|
|
833
872
|
}
|
|
873
|
+
function identityAttrs(args) {
|
|
874
|
+
return [
|
|
875
|
+
attrString("ai.telemetry.metadata.raindrop.ai.userId", args.userId),
|
|
876
|
+
attrString("ai.telemetry.metadata.raindrop.convoId", args.convoId),
|
|
877
|
+
attrString("ai.telemetry.metadata.raindrop.eventName", args.eventName)
|
|
878
|
+
];
|
|
879
|
+
}
|
|
834
880
|
function applyOtelSpanAttributeLimit(limit) {
|
|
835
881
|
var _a, _b;
|
|
836
882
|
try {
|
|
@@ -955,6 +1001,7 @@ var TraceShipper = class {
|
|
|
955
1001
|
const started = (_a = args.startTimeUnixNano) != null ? _a : nowUnixNanoString();
|
|
956
1002
|
const attrs = [
|
|
957
1003
|
attrString("ai.telemetry.metadata.raindrop.eventId", args.eventId),
|
|
1004
|
+
...identityAttrs(args),
|
|
958
1005
|
attrString("ai.operationId", args.operationId)
|
|
959
1006
|
];
|
|
960
1007
|
if ((_b = args.attributes) == null ? void 0 : _b.length) attrs.push(...args.attributes);
|
|
@@ -1010,7 +1057,8 @@ var TraceShipper = class {
|
|
|
1010
1057
|
var _a;
|
|
1011
1058
|
const ids = createSpanIds(args.parent);
|
|
1012
1059
|
const attrs = [
|
|
1013
|
-
attrString("ai.telemetry.metadata.raindrop.eventId", args.eventId)
|
|
1060
|
+
attrString("ai.telemetry.metadata.raindrop.eventId", args.eventId),
|
|
1061
|
+
...identityAttrs(args)
|
|
1014
1062
|
];
|
|
1015
1063
|
if ((_a = args.attributes) == null ? void 0 : _a.length) attrs.push(...args.attributes);
|
|
1016
1064
|
const otlp = buildOtlpSpan({
|
|
@@ -1229,7 +1277,7 @@ function resolveLocalWorkshopUrl(fileValue) {
|
|
|
1229
1277
|
// package.json
|
|
1230
1278
|
var package_default = {
|
|
1231
1279
|
name: "@raindrop-ai/opencode-plugin",
|
|
1232
|
-
version: "0.
|
|
1280
|
+
version: "0.1.1",
|
|
1233
1281
|
description: "Raindrop observability plugin for OpenCode \u2014 automatic session/event/span tracing",
|
|
1234
1282
|
type: "module",
|
|
1235
1283
|
main: "dist/index.js",
|
|
@@ -1509,6 +1557,8 @@ function createSessionState(sessionId) {
|
|
|
1509
1557
|
return {
|
|
1510
1558
|
sessionId,
|
|
1511
1559
|
currentInput: "",
|
|
1560
|
+
turnOutputParts: [],
|
|
1561
|
+
turnOutputTokens: 0,
|
|
1512
1562
|
outputParts: /* @__PURE__ */ new Map(),
|
|
1513
1563
|
reasoningParts: /* @__PURE__ */ new Map(),
|
|
1514
1564
|
toolSpanStarts: /* @__PURE__ */ new Map(),
|
|
@@ -1614,7 +1664,7 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
|
|
|
1614
1664
|
// event — session lifecycle + streaming parts
|
|
1615
1665
|
// ------------------------------------------------------------------
|
|
1616
1666
|
event: async ({ event }) => {
|
|
1617
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
1667
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
1618
1668
|
try {
|
|
1619
1669
|
const props = event.properties;
|
|
1620
1670
|
const info = props["info"];
|
|
@@ -1739,6 +1789,10 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
|
|
|
1739
1789
|
const outputText = state.outputParts.get(messageId);
|
|
1740
1790
|
const reasoningText = state.reasoningParts.get(messageId);
|
|
1741
1791
|
const isToolCallsCompletion = finishReason === "tool-calls";
|
|
1792
|
+
if (outputText !== void 0 && outputText.trim().length > 0) {
|
|
1793
|
+
state.turnOutputParts.push(outputText);
|
|
1794
|
+
}
|
|
1795
|
+
state.turnOutputTokens += outputTokens;
|
|
1742
1796
|
const hasIntermediateReasoning = reasoningText !== void 0 && reasoningText.trim().length > 0;
|
|
1743
1797
|
if (outputText === void 0 && !(isToolCallsCompletion && hasIntermediateReasoning))
|
|
1744
1798
|
return;
|
|
@@ -1793,6 +1847,7 @@ type: ${name != null ? name : "UnknownError"}`;
|
|
|
1793
1847
|
return;
|
|
1794
1848
|
}
|
|
1795
1849
|
if (outputText === void 0) return;
|
|
1850
|
+
const turnOutputText = capText2(state.turnOutputParts.join("\n\n"));
|
|
1796
1851
|
const rootSpan = state.currentRootSpan;
|
|
1797
1852
|
const isCompaction = state.isCompacting === true;
|
|
1798
1853
|
if (state.parentId) {
|
|
@@ -1804,7 +1859,7 @@ type: ${name != null ? name : "UnknownError"}`;
|
|
|
1804
1859
|
"ai.prompt.messages",
|
|
1805
1860
|
buildPromptMessages(state.currentSystemPrompt, state.currentInput)
|
|
1806
1861
|
),
|
|
1807
|
-
attrString("ai.response.text",
|
|
1862
|
+
attrString("ai.response.text", turnOutputText),
|
|
1808
1863
|
attrString("gen_ai.system", providerID),
|
|
1809
1864
|
attrString("gen_ai.request.model", modelID),
|
|
1810
1865
|
attrString("gen_ai.response.model", modelID),
|
|
@@ -1856,7 +1911,7 @@ type: ${name != null ? name : "UnknownError"}`;
|
|
|
1856
1911
|
"ai.prompt.messages",
|
|
1857
1912
|
buildPromptMessages(state.currentSystemPrompt, state.currentInput)
|
|
1858
1913
|
),
|
|
1859
|
-
attrString("ai.response.text",
|
|
1914
|
+
attrString("ai.response.text", turnOutputText),
|
|
1860
1915
|
attrString("gen_ai.system", providerID),
|
|
1861
1916
|
attrString("gen_ai.request.model", modelID),
|
|
1862
1917
|
attrString("gen_ai.response.model", modelID),
|
|
@@ -1885,8 +1940,8 @@ type: ${name != null ? name : "UnknownError"}`;
|
|
|
1885
1940
|
}
|
|
1886
1941
|
}
|
|
1887
1942
|
state.currentRootSpan = void 0;
|
|
1888
|
-
const hasAssistantResponse =
|
|
1889
|
-
if (!hasAssistantResponse) {
|
|
1943
|
+
const hasAssistantResponse = turnOutputText.trim().length > 0;
|
|
1944
|
+
if (!hasAssistantResponse && !msgError) {
|
|
1890
1945
|
await traceShipper.flush();
|
|
1891
1946
|
state.currentEventId = void 0;
|
|
1892
1947
|
return;
|
|
@@ -1895,7 +1950,15 @@ type: ${name != null ? name : "UnknownError"}`;
|
|
|
1895
1950
|
await eventShipper.finish(state.currentEventId, {
|
|
1896
1951
|
userId: (_d = (_c = state.eventMetadata) == null ? void 0 : _c.userId) != null ? _d : state.sessionId,
|
|
1897
1952
|
model: modelID,
|
|
1898
|
-
output:
|
|
1953
|
+
output: msgError ? void 0 : turnOutputText,
|
|
1954
|
+
usage: {
|
|
1955
|
+
promptTokens: inputTokens,
|
|
1956
|
+
completionTokens: state.turnOutputTokens
|
|
1957
|
+
},
|
|
1958
|
+
error: msgError ? {
|
|
1959
|
+
name: msgError.name,
|
|
1960
|
+
message: (_g = (_f = (_e = msgError.data) == null ? void 0 : _e.message) != null ? _f : msgError.name) != null ? _g : "OpenCode request failed"
|
|
1961
|
+
} : void 0,
|
|
1899
1962
|
properties: {
|
|
1900
1963
|
plugin_version: PLUGIN_VERSION,
|
|
1901
1964
|
message_id: messageId,
|
|
@@ -1939,7 +2002,7 @@ type: ${name != null ? name : "UnknownError"}`;
|
|
|
1939
2002
|
await Promise.all([eventShipper.flush(), traceShipper.flush()]);
|
|
1940
2003
|
sessions.delete(String(sessionID));
|
|
1941
2004
|
} else if (event.type === "session.error") {
|
|
1942
|
-
const errorSessionID = (
|
|
2005
|
+
const errorSessionID = (_h = props["sessionID"]) != null ? _h : sessionID;
|
|
1943
2006
|
if (errorSessionID === void 0 || errorSessionID === "")
|
|
1944
2007
|
throw new Error("session.error: missing sessionID");
|
|
1945
2008
|
const state = sessions.get(String(errorSessionID));
|
|
@@ -1947,7 +2010,7 @@ type: ${name != null ? name : "UnknownError"}`;
|
|
|
1947
2010
|
const errorObj = props["error"];
|
|
1948
2011
|
if (errorObj == null) throw new Error("session.error: missing error object");
|
|
1949
2012
|
const errorName = errorObj.name;
|
|
1950
|
-
const errorMessage = (
|
|
2013
|
+
const errorMessage = (_j = (_i = errorObj.data) == null ? void 0 : _i.message) != null ? _j : errorObj.name;
|
|
1951
2014
|
if (errorName == null && errorMessage == null)
|
|
1952
2015
|
throw new Error("session.error: error object must have name or data.message");
|
|
1953
2016
|
const errorStr = `${errorMessage != null ? errorMessage : errorName}
|
|
@@ -2024,6 +2087,8 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
2024
2087
|
};
|
|
2025
2088
|
const userText = capText2(textParts.join("\n"));
|
|
2026
2089
|
state.currentInput = userText;
|
|
2090
|
+
state.turnOutputParts = [];
|
|
2091
|
+
state.turnOutputTokens = 0;
|
|
2027
2092
|
if (state.parentId) {
|
|
2028
2093
|
if (!state.parentTaskSpanIds || !state.parentEventContext) {
|
|
2029
2094
|
return;
|
|
@@ -2241,6 +2306,21 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
2241
2306
|
}
|
|
2242
2307
|
|
|
2243
2308
|
// src/index.ts
|
|
2309
|
+
function createRaindropHandle(eventShipper, traceShipper) {
|
|
2310
|
+
return {
|
|
2311
|
+
users: {
|
|
2312
|
+
async identify(users) {
|
|
2313
|
+
await eventShipper.identify(users);
|
|
2314
|
+
}
|
|
2315
|
+
},
|
|
2316
|
+
async flush() {
|
|
2317
|
+
await Promise.all([eventShipper.flush(), traceShipper.flush()]);
|
|
2318
|
+
},
|
|
2319
|
+
async shutdown() {
|
|
2320
|
+
await Promise.all([eventShipper.shutdown(), traceShipper.shutdown()]);
|
|
2321
|
+
}
|
|
2322
|
+
};
|
|
2323
|
+
}
|
|
2244
2324
|
async function plugin(input) {
|
|
2245
2325
|
var _a;
|
|
2246
2326
|
const config = loadConfig(input.directory);
|
|
@@ -2286,7 +2366,7 @@ async function plugin(input) {
|
|
|
2286
2366
|
eventShipper,
|
|
2287
2367
|
traceShipper
|
|
2288
2368
|
);
|
|
2289
|
-
return hooks;
|
|
2369
|
+
return { ...hooks, raindrop: createRaindropHandle(eventShipper, traceShipper) };
|
|
2290
2370
|
}
|
|
2291
2371
|
export {
|
|
2292
2372
|
plugin as default
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@raindrop-ai/opencode-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Raindrop observability plugin for OpenCode — automatic session/event/span tracing",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"@types/node": "^20.11.17",
|
|
38
38
|
"tsup": "^8.4.0",
|
|
39
39
|
"typescript": "^5.3.3",
|
|
40
|
-
"@raindrop-ai/core": "0.1.
|
|
40
|
+
"@raindrop-ai/core": "0.1.3"
|
|
41
41
|
},
|
|
42
42
|
"tsup": {
|
|
43
43
|
"entry": [
|