@brunobracaioli/aibrain 0.2.0-beta.1 → 0.3.0-beta.13

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,305 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+
3
+ export const TELEMETRY_SCHEMA_VERSION = 1;
4
+ export const TELEMETRY_USAGE_THROTTLE_MS = 60_000;
5
+ export const TELEMETRY_FRESHNESS_MS = 180_000;
6
+ export const TELEMETRY_QUEUE_LIMIT = 32;
7
+ export const TELEMETRY_TIMEOUT_MS = 10_000;
8
+ export const TELEMETRY_FAILURE_COOLDOWN_MS = 60_000;
9
+
10
+ const CLIENTS = new Set(['claude-code', 'codex-cli']);
11
+ const SESSION_PHASES = new Set(['connected', 'usage', 'disconnected']);
12
+ const OPERATION_PHASES = new Set([
13
+ 'plan_prepared',
14
+ 'selection_completed',
15
+ 'notes_written',
16
+ 'native_completed',
17
+ 'recovery_attached',
18
+ 'applied',
19
+ 'fallback',
20
+ 'failed',
21
+ 'uncertain',
22
+ ]);
23
+ const REASONS = new Set([
24
+ 'provider_unavailable',
25
+ 'insufficient_reduction',
26
+ 'protected_context',
27
+ 'stale_context',
28
+ 'rate_limited',
29
+ 'native_error',
30
+ 'recovery_pending',
31
+ 'recovery_uncertain',
32
+ 'unsupported',
33
+ 'unknown',
34
+ ]);
35
+ const UNITS = new Set(['tokens', 'estimated_tokens', 'characters']);
36
+
37
+ function isRecord(value) {
38
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
39
+ }
40
+
41
+ function isUuid(value) {
42
+ return (
43
+ typeof value === 'string' &&
44
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(value)
45
+ );
46
+ }
47
+
48
+ function boundedVersion(value) {
49
+ return typeof value === 'string' &&
50
+ /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/u.test(value) &&
51
+ value.length <= 40
52
+ ? value
53
+ : null;
54
+ }
55
+
56
+ function hashSession(nativeSessionId) {
57
+ return createHash('sha256').update(String(nativeSessionId), 'utf8').digest('hex');
58
+ }
59
+
60
+ function measurement(value) {
61
+ if (
62
+ !isRecord(value) ||
63
+ !UNITS.has(value.unit) ||
64
+ !Number.isSafeInteger(value.used) ||
65
+ value.used < 0
66
+ )
67
+ return null;
68
+ if (
69
+ value.capacity !== undefined &&
70
+ (!Number.isSafeInteger(value.capacity) || value.capacity <= 0)
71
+ )
72
+ return null;
73
+ return {
74
+ unit: value.unit,
75
+ used: value.used,
76
+ ...(value.capacity === undefined ? {} : { capacity: value.capacity }),
77
+ };
78
+ }
79
+
80
+ function counts(value) {
81
+ if (!isRecord(value)) return undefined;
82
+ const result = {};
83
+ for (const key of ['eligible', 'protected', 'kept', 'reduced', 'dropped', 'selected']) {
84
+ if (value[key] === undefined) continue;
85
+ if (!Number.isSafeInteger(value[key]) || value[key] < 0 || value[key] > 1_000_000) return null;
86
+ result[key] = value[key];
87
+ }
88
+ return result;
89
+ }
90
+
91
+ function normalizeEvent(event, context) {
92
+ if (!isRecord(event) || typeof event.phase !== 'string') return null;
93
+ if (!SESSION_PHASES.has(event.phase) && !OPERATION_PHASES.has(event.phase)) return null;
94
+ const operation = OPERATION_PHASES.has(event.phase);
95
+ if (operation && !isUuid(event.operationId)) return null;
96
+ if (!SESSION_PHASES.has(event.phase) && !CLIENTS.has(context.client)) return null;
97
+ const result = {
98
+ schema_version: TELEMETRY_SCHEMA_VERSION,
99
+ event_id: randomUUID(),
100
+ session_key: context.sessionKey,
101
+ instance_id: context.instanceId,
102
+ sequence: context.nextSequence(),
103
+ observed_at: new Date(context.now()).toISOString(),
104
+ client: context.client,
105
+ client_version: context.clientVersion,
106
+ phase: event.phase,
107
+ };
108
+ if (operation) result.operation_id = event.operationId;
109
+ if (event.triggerPercent !== undefined) {
110
+ if (
111
+ typeof event.triggerPercent !== 'number' ||
112
+ !Number.isFinite(event.triggerPercent) ||
113
+ event.triggerPercent < 1 ||
114
+ event.triggerPercent > 95
115
+ )
116
+ return null;
117
+ result.trigger_percent = event.triggerPercent;
118
+ }
119
+ for (const key of ['measurement', 'before', 'after']) {
120
+ if (event[key] === undefined) continue;
121
+ const value = measurement(event[key]);
122
+ if (value === null) return null;
123
+ result[key] = value;
124
+ }
125
+ if (event.measurement !== undefined && event.phase === 'usage' && !result.measurement)
126
+ return null;
127
+ if (event.before !== undefined && event.after !== undefined) {
128
+ if (
129
+ result.before.unit !== result.after.unit ||
130
+ result.before.capacity !== result.after.capacity
131
+ )
132
+ return null;
133
+ }
134
+ if (event.counts !== undefined) {
135
+ const value = counts(event.counts);
136
+ if (value === null) return null;
137
+ result.counts = value;
138
+ }
139
+ if (event.reason !== undefined) {
140
+ if (!REASONS.has(event.reason)) return null;
141
+ result.reason = event.reason;
142
+ }
143
+ return result;
144
+ }
145
+
146
+ function withTimeout(promise, timeoutMs, setTimer, clearTimer) {
147
+ let timer;
148
+ return Promise.race([
149
+ Promise.resolve(promise),
150
+ new Promise((_, reject) => {
151
+ timer = setTimer(() => reject(new Error('telemetry_timeout')), timeoutMs);
152
+ }),
153
+ ]).finally(() => clearTimer(timer));
154
+ }
155
+
156
+ /**
157
+ * Create an opt-in reporter. `send` must use the adapter's existing authenticated
158
+ * MCP transport. Events are generated once and are never retried with a new id.
159
+ */
160
+ export function createTelemetryReporter({
161
+ client,
162
+ clientVersion,
163
+ sessionId,
164
+ enabled = false,
165
+ disclosed = false,
166
+ send,
167
+ now = () => Date.now(),
168
+ timeoutMs = TELEMETRY_TIMEOUT_MS,
169
+ queueLimit = TELEMETRY_QUEUE_LIMIT,
170
+ setTimer = setTimeout,
171
+ clearTimer = clearTimeout,
172
+ } = {}) {
173
+ const version = boundedVersion(clientVersion);
174
+ const validSession =
175
+ typeof sessionId === 'string' && sessionId.length > 0 && sessionId.length <= 256;
176
+ const active =
177
+ enabled === true &&
178
+ disclosed === true &&
179
+ CLIENTS.has(client) &&
180
+ version !== null &&
181
+ validSession &&
182
+ typeof send === 'function';
183
+ const sessionKey = validSession ? hashSession(sessionId) : '';
184
+ const instanceId = randomUUID();
185
+ let sequence = 0;
186
+ let stopped = false;
187
+ let busy = false;
188
+ let lastUsageAt = -Infinity;
189
+ let retryAfter = -Infinity;
190
+ const queue = [];
191
+ const drainWaiters = new Set();
192
+
193
+ function resolveDrainWaiters() {
194
+ if (busy || queue.length > 0) return;
195
+ for (const resolve of drainWaiters) resolve();
196
+ drainWaiters.clear();
197
+ }
198
+
199
+ function nextSequence() {
200
+ sequence += 1;
201
+ return sequence;
202
+ }
203
+
204
+ function drain() {
205
+ if (busy || stopped || queue.length === 0) return;
206
+ busy = true;
207
+ const event = queue.shift();
208
+ void withTimeout(
209
+ Promise.resolve().then(() => send(event)),
210
+ timeoutMs,
211
+ setTimer,
212
+ clearTimer,
213
+ )
214
+ .catch(() => {
215
+ // An unknown receipt cannot be retried under a new event id. Drop the
216
+ // failed event and observations queued behind it, then allow fresh
217
+ // observations after a bounded cooldown.
218
+ queue.length = 0;
219
+ retryAfter = now() + TELEMETRY_FAILURE_COOLDOWN_MS;
220
+ })
221
+ .finally(() => {
222
+ busy = false;
223
+ resolveDrainWaiters();
224
+ drain();
225
+ });
226
+ }
227
+
228
+ function emit(event, { essential = false, throttleUsage = false } = {}) {
229
+ const currentTime = now();
230
+ if (!active || stopped || currentTime < retryAfter) return false;
231
+ if (throttleUsage && currentTime - lastUsageAt < TELEMETRY_USAGE_THROTTLE_MS) return false;
232
+ const normalized = normalizeEvent(event, {
233
+ client,
234
+ clientVersion: version,
235
+ sessionKey,
236
+ instanceId,
237
+ now,
238
+ nextSequence,
239
+ });
240
+ if (!normalized) return false;
241
+ if (throttleUsage) lastUsageAt = currentTime;
242
+ if (queue.length >= queueLimit) {
243
+ const removable = queue.findIndex((item) => item.phase === 'usage');
244
+ if (removable >= 0) queue.splice(removable, 1);
245
+ else if (!essential) return false;
246
+ else queue.shift();
247
+ }
248
+ queue.push(normalized);
249
+ drain();
250
+ return true;
251
+ }
252
+
253
+ function stop() {
254
+ stopped = true;
255
+ queue.length = 0;
256
+ resolveDrainWaiters();
257
+ }
258
+
259
+ function flush(timeoutMs = TELEMETRY_TIMEOUT_MS) {
260
+ if (!busy && queue.length === 0) return Promise.resolve();
261
+ return new Promise((resolve) => {
262
+ let timer;
263
+ const complete = () => {
264
+ if (timer !== undefined) clearTimer(timer);
265
+ drainWaiters.delete(complete);
266
+ resolve();
267
+ };
268
+ timer = setTimer(() => {
269
+ // A flush timeout only discards stale queued work. Future lifecycle
270
+ // hooks remain eligible; explicit stop() is the terminal operation.
271
+ queue.length = 0;
272
+ complete();
273
+ }, timeoutMs);
274
+ drainWaiters.add(complete);
275
+ });
276
+ }
277
+
278
+ return Object.freeze({
279
+ active,
280
+ sessionKey,
281
+ instanceId,
282
+ emit,
283
+ connected: (fields = {}) => emit({ ...fields, phase: 'connected' }, { essential: true }),
284
+ usage: (fields = {}) => emit({ ...fields, phase: 'usage' }, { throttleUsage: true }),
285
+ disconnected: (fields = {}) => emit({ ...fields, phase: 'disconnected' }, { essential: true }),
286
+ planPrepared: (fields) => emit({ ...fields, phase: 'plan_prepared' }),
287
+ selectionCompleted: (fields) => emit({ ...fields, phase: 'selection_completed' }),
288
+ notesWritten: (fields) => emit({ ...fields, phase: 'notes_written' }),
289
+ nativeCompleted: (fields) => emit({ ...fields, phase: 'native_completed' }),
290
+ recoveryAttached: (fields) => emit({ ...fields, phase: 'recovery_attached' }),
291
+ fallback: (fields) => emit({ ...fields, phase: 'fallback' }),
292
+ failed: (fields) => emit({ ...fields, phase: 'failed' }),
293
+ uncertain: (fields) => emit({ ...fields, phase: 'uncertain' }),
294
+ flush,
295
+ stop,
296
+ });
297
+ }
298
+
299
+ export const telemetryInternals = {
300
+ boundedVersion,
301
+ counts,
302
+ hashSession,
303
+ measurement,
304
+ normalizeEvent,
305
+ };