@vymalo/opencode-core-otel 0.14.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.
@@ -0,0 +1,166 @@
1
+ import { type Context } from "@opentelemetry/api";
2
+ import type { Hooks } from "@opencode-ai/plugin";
3
+ import type { Logger } from "./logging.js";
4
+ import type { TelemetryProviders } from "./providers.js";
5
+ import type { ResolvedOtelConfig } from "./types.js";
6
+ type OpencodeEvent = Parameters<NonNullable<Hooks["event"]>>[0]["event"];
7
+ type ChatMessageInput = Parameters<NonNullable<Hooks["chat.message"]>>[0];
8
+ type ChatMessageOutput = Parameters<NonNullable<Hooks["chat.message"]>>[1];
9
+ type ToolBeforeInput = Parameters<NonNullable<Hooks["tool.execute.before"]>>[0];
10
+ type ToolAfterInput = Parameters<NonNullable<Hooks["tool.execute.after"]>>[0];
11
+ type ChatParamsInput = Parameters<NonNullable<Hooks["chat.params"]>>[0];
12
+ type ChatParamsOutput = Parameters<NonNullable<Hooks["chat.params"]>>[1];
13
+ type TextCompleteInput = Parameters<NonNullable<Hooks["experimental.text.complete"]>>[0];
14
+ type PermissionAskInput = Parameters<NonNullable<Hooks["permission.ask"]>>[0];
15
+ type CompactionInput = Parameters<NonNullable<Hooks["experimental.compaction.autocontinue"]>>[0];
16
+ export interface RecorderDeps {
17
+ providers: TelemetryProviders;
18
+ config: ResolvedOtelConfig;
19
+ logger: Logger;
20
+ /** Injectable clock; defaults to `Date.now`. */
21
+ now?: () => number;
22
+ /**
23
+ * Sinks for the two resource attributes OpenCode only reveals through
24
+ * events (`installation.updated`, `vcs.branch.updated`). Without these the
25
+ * recorder has nowhere to put them: a `Resource` is fixed at provider
26
+ * construction, which happens before any event arrives.
27
+ */
28
+ resourceSinks?: {
29
+ version?: (value: string) => void;
30
+ branch?: (value: string) => void;
31
+ };
32
+ }
33
+ /**
34
+ * Translates the OpenCode event stream and hook callbacks into OTel signals.
35
+ *
36
+ * Deliberately holds no content: lengths, counts, durations and outcomes only.
37
+ * See `plans/otel.md` → "No content capture in v1".
38
+ */
39
+ export declare class TelemetryRecorder {
40
+ private readonly deps;
41
+ private readonly instruments?;
42
+ private readonly now;
43
+ private readonly sessions;
44
+ private readonly chats;
45
+ /**
46
+ * Chats opened by `chat.params` but not yet matched to an assistant message.
47
+ * `chat.params` fires *before* the provider request goes out, so without this
48
+ * the very first request of a turn has no span to propagate trace context
49
+ * from. Keyed by session; adopted by the next assistant message.
50
+ */
51
+ private readonly pendingChats;
52
+ private readonly tools;
53
+ /** Completed assistant text length per message, from `experimental.text.complete`. */
54
+ private readonly responseLengths;
55
+ /** Permissions already counted at `permission.ask` time, so a later reply cannot double-count. */
56
+ private readonly autoDecided;
57
+ /**
58
+ * Terminal tool outcomes already recorded, so the hook and the part update
59
+ * cannot double-count. Keyed by call id, valued by session so the entry can
60
+ * be pruned when that session ends.
61
+ */
62
+ private readonly finishedTools;
63
+ /**
64
+ * Assistant messages already finalized — `message.updated` fires repeatedly
65
+ * with cumulative totals. Valued by session, for pruning.
66
+ */
67
+ private readonly finalizedMessages;
68
+ /** Pending permission prompts, so `permission.replied` can name the tool it resolved. */
69
+ private readonly permissions;
70
+ /**
71
+ * Last-seen cumulative diff per `sessionID\0file`. `session.diff` reports the
72
+ * session's whole diff each time, so only the delta may be counted.
73
+ */
74
+ private readonly diffs;
75
+ constructor(deps: RecorderDeps);
76
+ /**
77
+ * Session id as a *metric* attribute — omitted unless `includeSessionId`,
78
+ * because it is unbounded cardinality and metric backends bill per series.
79
+ * Logs and spans always carry it.
80
+ */
81
+ private metricSession;
82
+ private emit;
83
+ private session;
84
+ onEvent(event: OpencodeEvent): void;
85
+ private dispatch;
86
+ private onSessionCreated;
87
+ private onSessionStatus;
88
+ private settleActiveTime;
89
+ private onSessionIdle;
90
+ private onCompacted;
91
+ private onSessionError;
92
+ private onSessionDiff;
93
+ private onMessageUpdated;
94
+ private finalizeMessage;
95
+ private onPartUpdated;
96
+ private onPermissionReplied;
97
+ private recordDecision;
98
+ /**
99
+ * Drop every per-session entry. Without this the bookkeeping sets grow for
100
+ * the life of the process — fine for a CLI invocation, a slow leak in a
101
+ * long-running OpenCode server.
102
+ */
103
+ private forgetSession;
104
+ /**
105
+ * Drop the dedupe bookkeeping for a session's finished messages and tool
106
+ * calls. Safe to run at every idle — those ids are never reused, so nothing
107
+ * can be double-counted afterwards. Deliberately does **not** touch `diffs`:
108
+ * `session.diff` is cumulative, so forgetting the last-seen totals for a
109
+ * session that later resumes would re-count its whole diff.
110
+ */
111
+ private pruneCompleted;
112
+ private onCommandExecuted;
113
+ private onTodoUpdated;
114
+ onChatMessage(input: ChatMessageInput, output: ChatMessageOutput): void;
115
+ /**
116
+ * `chat.params` runs immediately before the provider request. Opening the
117
+ * `chat` span here rather than at the first `message.updated` is what makes
118
+ * trace-context propagation work at all for the first request of a turn —
119
+ * otherwise the fetch happens while no chat span exists.
120
+ */
121
+ onChatParams(input: ChatParamsInput, output: ChatParamsOutput): void;
122
+ /** Assistant text finished streaming — record its size, never its content. */
123
+ onTextComplete(input: TextCompleteInput, output: {
124
+ text?: string;
125
+ }): void;
126
+ /**
127
+ * Every permission evaluation passes through here, including the ones config
128
+ * auto-resolves. Only an already-decided prompt is counted now — an `ask`
129
+ * waits for `permission.replied`, so the two paths never double-count. Without
130
+ * this hook, auto-allowed permissions were invisible and the decision counter
131
+ * silently undercounted.
132
+ */
133
+ onPermissionAsk(input: PermissionAskInput, output: {
134
+ status?: string;
135
+ }): void;
136
+ /** Compaction finished — `overflow` says whether the context forced it. */
137
+ onCompactionAutocontinue(input: CompactionInput, output: {
138
+ enabled?: boolean;
139
+ }): void;
140
+ onToolBefore(input: ToolBeforeInput): void;
141
+ onToolAfter(input: ToolAfterInput, output: {
142
+ output?: string;
143
+ }): void;
144
+ /**
145
+ * Record a tool's terminal outcome exactly once. Both `tool.execute.after`
146
+ * and the tool part reaching a terminal state report it, and which arrives
147
+ * depends on whether the tool succeeded — first writer wins, so a failing
148
+ * tool (no `after` hook) is still counted.
149
+ */
150
+ private finishTool;
151
+ /**
152
+ * The context of the single in-flight chat, or `undefined` when zero or more
153
+ * than one is running. Ambiguity yields no trace context rather than a wrong
154
+ * parent — a missing link is recoverable, a fabricated one is not.
155
+ */
156
+ currentChatContext(): Context | undefined;
157
+ /**
158
+ * Sizes of the in-memory bookkeeping. Exposed because these maps are the only
159
+ * unbounded thing the plugin holds — a long-running OpenCode server that
160
+ * never emitted `session.deleted` is exactly where a leak would show up, and
161
+ * "how big is it" should be answerable without a heap dump.
162
+ */
163
+ pendingStateSize(): Record<string, number>;
164
+ shutdown(): Promise<void>;
165
+ }
166
+ export {};