@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 CHANGED
@@ -24,7 +24,7 @@ __export(index_exports, {
24
24
  });
25
25
  module.exports = __toCommonJS(index_exports);
26
26
 
27
- // ../core/dist/chunk-267IXHO5.js
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 = { ...(_c = target.featureFlags) != null ? _c : {}, ...(_d = source.featureFlags) != null ? _d : {} };
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 = [...(_e = target.attachments) != null ? _e : [], ...(_f = source.attachments) != null ? _f : []];
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.19",
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 = (_e = props["sessionID"]) != null ? _e : sessionID;
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 = (_g = (_f = errorObj.data) == null ? void 0 : _f.message) != null ? _g : errorObj.name;
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 { PluginInput, Hooks } from '@opencode-ai/plugin';
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<Hooks>;
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 { PluginInput, Hooks } from '@opencode-ai/plugin';
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<Hooks>;
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-267IXHO5.js
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 = { ...(_c = target.featureFlags) != null ? _c : {}, ...(_d = source.featureFlags) != null ? _d : {} };
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 = [...(_e = target.attachments) != null ? _e : [], ...(_f = source.attachments) != null ? _f : []];
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.19",
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 = (_e = props["sessionID"]) != null ? _e : sessionID;
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 = (_g = (_f = errorObj.data) == null ? void 0 : _f.message) != null ? _g : errorObj.name;
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.19",
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.2"
40
+ "@raindrop-ai/core": "0.1.3"
41
41
  },
42
42
  "tsup": {
43
43
  "entry": [