@raindrop-ai/opencode-plugin 0.0.19 → 0.1.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/dist/index.cjs +84 -13
- package/dist/index.d.cts +105 -3
- package/dist/index.d.ts +105 -3
- package/dist/index.js +84 -13
- 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.0
|
|
1299
|
+
version: "0.1.0",
|
|
1252
1300
|
description: "Raindrop observability plugin for OpenCode \u2014 automatic session/event/span tracing",
|
|
1253
1301
|
type: "module",
|
|
1254
1302
|
main: "dist/index.js",
|
|
@@ -1633,7 +1681,7 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
|
|
|
1633
1681
|
// event — session lifecycle + streaming parts
|
|
1634
1682
|
// ------------------------------------------------------------------
|
|
1635
1683
|
event: async ({ event }) => {
|
|
1636
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
1684
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
1637
1685
|
try {
|
|
1638
1686
|
const props = event.properties;
|
|
1639
1687
|
const info = props["info"];
|
|
@@ -1905,7 +1953,7 @@ type: ${name != null ? name : "UnknownError"}`;
|
|
|
1905
1953
|
}
|
|
1906
1954
|
state.currentRootSpan = void 0;
|
|
1907
1955
|
const hasAssistantResponse = outputText.trim().length > 0;
|
|
1908
|
-
if (!hasAssistantResponse) {
|
|
1956
|
+
if (!hasAssistantResponse && !msgError) {
|
|
1909
1957
|
await traceShipper.flush();
|
|
1910
1958
|
state.currentEventId = void 0;
|
|
1911
1959
|
return;
|
|
@@ -1914,7 +1962,15 @@ type: ${name != null ? name : "UnknownError"}`;
|
|
|
1914
1962
|
await eventShipper.finish(state.currentEventId, {
|
|
1915
1963
|
userId: (_d = (_c = state.eventMetadata) == null ? void 0 : _c.userId) != null ? _d : state.sessionId,
|
|
1916
1964
|
model: modelID,
|
|
1917
|
-
output: outputText,
|
|
1965
|
+
output: msgError ? void 0 : outputText,
|
|
1966
|
+
usage: {
|
|
1967
|
+
promptTokens: inputTokens,
|
|
1968
|
+
completionTokens: outputTokens
|
|
1969
|
+
},
|
|
1970
|
+
error: msgError ? {
|
|
1971
|
+
name: msgError.name,
|
|
1972
|
+
message: (_g = (_f = (_e = msgError.data) == null ? void 0 : _e.message) != null ? _f : msgError.name) != null ? _g : "OpenCode request failed"
|
|
1973
|
+
} : void 0,
|
|
1918
1974
|
properties: {
|
|
1919
1975
|
plugin_version: PLUGIN_VERSION,
|
|
1920
1976
|
message_id: messageId,
|
|
@@ -1958,7 +2014,7 @@ type: ${name != null ? name : "UnknownError"}`;
|
|
|
1958
2014
|
await Promise.all([eventShipper.flush(), traceShipper.flush()]);
|
|
1959
2015
|
sessions.delete(String(sessionID));
|
|
1960
2016
|
} else if (event.type === "session.error") {
|
|
1961
|
-
const errorSessionID = (
|
|
2017
|
+
const errorSessionID = (_h = props["sessionID"]) != null ? _h : sessionID;
|
|
1962
2018
|
if (errorSessionID === void 0 || errorSessionID === "")
|
|
1963
2019
|
throw new Error("session.error: missing sessionID");
|
|
1964
2020
|
const state = sessions.get(String(errorSessionID));
|
|
@@ -1966,7 +2022,7 @@ type: ${name != null ? name : "UnknownError"}`;
|
|
|
1966
2022
|
const errorObj = props["error"];
|
|
1967
2023
|
if (errorObj == null) throw new Error("session.error: missing error object");
|
|
1968
2024
|
const errorName = errorObj.name;
|
|
1969
|
-
const errorMessage = (
|
|
2025
|
+
const errorMessage = (_j = (_i = errorObj.data) == null ? void 0 : _i.message) != null ? _j : errorObj.name;
|
|
1970
2026
|
if (errorName == null && errorMessage == null)
|
|
1971
2027
|
throw new Error("session.error: error object must have name or data.message");
|
|
1972
2028
|
const errorStr = `${errorMessage != null ? errorMessage : errorName}
|
|
@@ -2260,6 +2316,21 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
2260
2316
|
}
|
|
2261
2317
|
|
|
2262
2318
|
// src/index.ts
|
|
2319
|
+
function createRaindropHandle(eventShipper, traceShipper) {
|
|
2320
|
+
return {
|
|
2321
|
+
users: {
|
|
2322
|
+
async identify(users) {
|
|
2323
|
+
await eventShipper.identify(users);
|
|
2324
|
+
}
|
|
2325
|
+
},
|
|
2326
|
+
async flush() {
|
|
2327
|
+
await Promise.all([eventShipper.flush(), traceShipper.flush()]);
|
|
2328
|
+
},
|
|
2329
|
+
async shutdown() {
|
|
2330
|
+
await Promise.all([eventShipper.shutdown(), traceShipper.shutdown()]);
|
|
2331
|
+
}
|
|
2332
|
+
};
|
|
2333
|
+
}
|
|
2263
2334
|
async function plugin(input) {
|
|
2264
2335
|
var _a;
|
|
2265
2336
|
const config = loadConfig(input.directory);
|
|
@@ -2305,5 +2376,5 @@ async function plugin(input) {
|
|
|
2305
2376
|
eventShipper,
|
|
2306
2377
|
traceShipper
|
|
2307
2378
|
);
|
|
2308
|
-
return hooks;
|
|
2379
|
+
return { ...hooks, raindrop: createRaindropHandle(eventShipper, traceShipper) };
|
|
2309
2380
|
}
|
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.0
|
|
1280
|
+
version: "0.1.0",
|
|
1233
1281
|
description: "Raindrop observability plugin for OpenCode \u2014 automatic session/event/span tracing",
|
|
1234
1282
|
type: "module",
|
|
1235
1283
|
main: "dist/index.js",
|
|
@@ -1614,7 +1662,7 @@ function createHooks(config, worktree, directory, eventShipper, traceShipper) {
|
|
|
1614
1662
|
// event — session lifecycle + streaming parts
|
|
1615
1663
|
// ------------------------------------------------------------------
|
|
1616
1664
|
event: async ({ event }) => {
|
|
1617
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
1665
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
1618
1666
|
try {
|
|
1619
1667
|
const props = event.properties;
|
|
1620
1668
|
const info = props["info"];
|
|
@@ -1886,7 +1934,7 @@ type: ${name != null ? name : "UnknownError"}`;
|
|
|
1886
1934
|
}
|
|
1887
1935
|
state.currentRootSpan = void 0;
|
|
1888
1936
|
const hasAssistantResponse = outputText.trim().length > 0;
|
|
1889
|
-
if (!hasAssistantResponse) {
|
|
1937
|
+
if (!hasAssistantResponse && !msgError) {
|
|
1890
1938
|
await traceShipper.flush();
|
|
1891
1939
|
state.currentEventId = void 0;
|
|
1892
1940
|
return;
|
|
@@ -1895,7 +1943,15 @@ type: ${name != null ? name : "UnknownError"}`;
|
|
|
1895
1943
|
await eventShipper.finish(state.currentEventId, {
|
|
1896
1944
|
userId: (_d = (_c = state.eventMetadata) == null ? void 0 : _c.userId) != null ? _d : state.sessionId,
|
|
1897
1945
|
model: modelID,
|
|
1898
|
-
output: outputText,
|
|
1946
|
+
output: msgError ? void 0 : outputText,
|
|
1947
|
+
usage: {
|
|
1948
|
+
promptTokens: inputTokens,
|
|
1949
|
+
completionTokens: outputTokens
|
|
1950
|
+
},
|
|
1951
|
+
error: msgError ? {
|
|
1952
|
+
name: msgError.name,
|
|
1953
|
+
message: (_g = (_f = (_e = msgError.data) == null ? void 0 : _e.message) != null ? _f : msgError.name) != null ? _g : "OpenCode request failed"
|
|
1954
|
+
} : void 0,
|
|
1899
1955
|
properties: {
|
|
1900
1956
|
plugin_version: PLUGIN_VERSION,
|
|
1901
1957
|
message_id: messageId,
|
|
@@ -1939,7 +1995,7 @@ type: ${name != null ? name : "UnknownError"}`;
|
|
|
1939
1995
|
await Promise.all([eventShipper.flush(), traceShipper.flush()]);
|
|
1940
1996
|
sessions.delete(String(sessionID));
|
|
1941
1997
|
} else if (event.type === "session.error") {
|
|
1942
|
-
const errorSessionID = (
|
|
1998
|
+
const errorSessionID = (_h = props["sessionID"]) != null ? _h : sessionID;
|
|
1943
1999
|
if (errorSessionID === void 0 || errorSessionID === "")
|
|
1944
2000
|
throw new Error("session.error: missing sessionID");
|
|
1945
2001
|
const state = sessions.get(String(errorSessionID));
|
|
@@ -1947,7 +2003,7 @@ type: ${name != null ? name : "UnknownError"}`;
|
|
|
1947
2003
|
const errorObj = props["error"];
|
|
1948
2004
|
if (errorObj == null) throw new Error("session.error: missing error object");
|
|
1949
2005
|
const errorName = errorObj.name;
|
|
1950
|
-
const errorMessage = (
|
|
2006
|
+
const errorMessage = (_j = (_i = errorObj.data) == null ? void 0 : _i.message) != null ? _j : errorObj.name;
|
|
1951
2007
|
if (errorName == null && errorMessage == null)
|
|
1952
2008
|
throw new Error("session.error: error object must have name or data.message");
|
|
1953
2009
|
const errorStr = `${errorMessage != null ? errorMessage : errorName}
|
|
@@ -2241,6 +2297,21 @@ type: ${errorName != null ? errorName : "UnknownError"}`;
|
|
|
2241
2297
|
}
|
|
2242
2298
|
|
|
2243
2299
|
// src/index.ts
|
|
2300
|
+
function createRaindropHandle(eventShipper, traceShipper) {
|
|
2301
|
+
return {
|
|
2302
|
+
users: {
|
|
2303
|
+
async identify(users) {
|
|
2304
|
+
await eventShipper.identify(users);
|
|
2305
|
+
}
|
|
2306
|
+
},
|
|
2307
|
+
async flush() {
|
|
2308
|
+
await Promise.all([eventShipper.flush(), traceShipper.flush()]);
|
|
2309
|
+
},
|
|
2310
|
+
async shutdown() {
|
|
2311
|
+
await Promise.all([eventShipper.shutdown(), traceShipper.shutdown()]);
|
|
2312
|
+
}
|
|
2313
|
+
};
|
|
2314
|
+
}
|
|
2244
2315
|
async function plugin(input) {
|
|
2245
2316
|
var _a;
|
|
2246
2317
|
const config = loadConfig(input.directory);
|
|
@@ -2286,7 +2357,7 @@ async function plugin(input) {
|
|
|
2286
2357
|
eventShipper,
|
|
2287
2358
|
traceShipper
|
|
2288
2359
|
);
|
|
2289
|
-
return hooks;
|
|
2360
|
+
return { ...hooks, raindrop: createRaindropHandle(eventShipper, traceShipper) };
|
|
2290
2361
|
}
|
|
2291
2362
|
export {
|
|
2292
2363
|
plugin as default
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@raindrop-ai/opencode-plugin",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
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": [
|