ardea 0.2.0 → 0.3.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.js ADDED
@@ -0,0 +1,396 @@
1
+ import {
2
+ DEFAULT_ENDPOINT
3
+ } from "./chunk-UL6WUTYP.js";
4
+
5
+ // src/buffer.ts
6
+ import { gzipSync } from "zlib";
7
+ import https from "https";
8
+ import http from "http";
9
+ var MAX_LEN = 1e4;
10
+ var BATCH_SIZE = 100;
11
+ var FLUSH_INTERVAL_MS = 1e4;
12
+ var MAX_EVENTS_PER_SESSION = 500;
13
+ var MAX_SESSION_CACHE_EVENTS = 5e3;
14
+ var RETRY_ATTEMPTS = 3;
15
+ var RETRY_BACKOFF_MS = [1e3, 2e3, 4e3];
16
+ var EventBuffer = class {
17
+ _buffer = [];
18
+ _sessionCache = /* @__PURE__ */ new Map();
19
+ _sessionCacheEventCount = 0;
20
+ _apiKey;
21
+ _endpoint;
22
+ _timer = null;
23
+ _stopped = false;
24
+ /** Reference to the original (unpatched) https.request for anti-recursion. */
25
+ _originalHttpsRequest;
26
+ /** Reference to the original (unpatched) http.request for anti-recursion. */
27
+ _originalHttpRequest;
28
+ constructor(apiKey, endpoint = DEFAULT_ENDPOINT, autoFlush = true, originalHttpsRequest, originalHttpRequest) {
29
+ this._apiKey = apiKey;
30
+ this._endpoint = endpoint.replace(/\/+$/, "");
31
+ this._originalHttpsRequest = originalHttpsRequest ?? https.request;
32
+ this._originalHttpRequest = originalHttpRequest ?? http.request;
33
+ if (autoFlush) {
34
+ this._timer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS);
35
+ this._timer.unref();
36
+ }
37
+ }
38
+ /** Add an event to the ring buffer. */
39
+ push(event) {
40
+ if (this._buffer.length >= MAX_LEN) {
41
+ this._buffer.shift();
42
+ }
43
+ this._buffer.push(event);
44
+ const sessionId = event.framework_session_id;
45
+ if (typeof sessionId === "string" && sessionId) {
46
+ if (this._sessionCacheEventCount < MAX_SESSION_CACHE_EVENTS) {
47
+ let sessionEvents = this._sessionCache.get(sessionId);
48
+ if (!sessionEvents) {
49
+ sessionEvents = [];
50
+ this._sessionCache.set(sessionId, sessionEvents);
51
+ }
52
+ if (sessionEvents.length < MAX_EVENTS_PER_SESSION) {
53
+ sessionEvents.push(event);
54
+ this._sessionCacheEventCount++;
55
+ }
56
+ }
57
+ }
58
+ }
59
+ /** Return a copy of events for the given session from the cache. */
60
+ getSessionEvents(sessionId) {
61
+ return [...this._sessionCache.get(sessionId) ?? []];
62
+ }
63
+ /** Remove session events from the cache. */
64
+ clearSession(sessionId) {
65
+ const events = this._sessionCache.get(sessionId);
66
+ if (events) {
67
+ this._sessionCacheEventCount = Math.max(
68
+ 0,
69
+ this._sessionCacheEventCount - events.length
70
+ );
71
+ this._sessionCache.delete(sessionId);
72
+ }
73
+ }
74
+ /** Remove and return up to `count` events from the buffer. */
75
+ drain(count) {
76
+ const n = Math.min(count, this._buffer.length);
77
+ return this._buffer.splice(0, n);
78
+ }
79
+ /** Get all buffered events (without draining). For reporter access. */
80
+ peek() {
81
+ return [...this._buffer];
82
+ }
83
+ /** Number of buffered events. */
84
+ get length() {
85
+ return this._buffer.length;
86
+ }
87
+ /** Drain a batch and send to backend. */
88
+ async flush() {
89
+ const events = this.drain(BATCH_SIZE);
90
+ if (events.length === 0) return;
91
+ for (let attempt = 0; attempt < RETRY_ATTEMPTS; attempt++) {
92
+ try {
93
+ await this._send(events);
94
+ return;
95
+ } catch {
96
+ if (attempt < RETRY_ATTEMPTS - 1) {
97
+ await sleep(RETRY_BACKOFF_MS[attempt]);
98
+ }
99
+ }
100
+ }
101
+ }
102
+ /** Stop flush timer and drain all remaining events. */
103
+ async shutdown() {
104
+ this._stopped = true;
105
+ if (this._timer) {
106
+ clearInterval(this._timer);
107
+ this._timer = null;
108
+ }
109
+ while (this._buffer.length > 0) {
110
+ const events = this.drain(BATCH_SIZE);
111
+ if (events.length === 0) break;
112
+ for (let attempt = 0; attempt < RETRY_ATTEMPTS; attempt++) {
113
+ try {
114
+ await this._send(events);
115
+ break;
116
+ } catch {
117
+ if (attempt === RETRY_ATTEMPTS - 1) {
118
+ } else {
119
+ await sleep(RETRY_BACKOFF_MS[attempt]);
120
+ }
121
+ }
122
+ }
123
+ }
124
+ }
125
+ /** POST events to the backend with gzip compression. */
126
+ _send(events) {
127
+ const payload = JSON.stringify({
128
+ events,
129
+ sdk_version: "0.1.0"
130
+ });
131
+ const compressed = gzipSync(Buffer.from(payload, "utf-8"));
132
+ const url = new URL(`${this._endpoint}/v1/events`);
133
+ const isHttps = url.protocol === "https:";
134
+ const requestFn = isHttps ? this._originalHttpsRequest : this._originalHttpRequest;
135
+ return new Promise((resolve, reject) => {
136
+ const req = requestFn(
137
+ {
138
+ hostname: url.hostname,
139
+ port: url.port || (isHttps ? 443 : 80),
140
+ path: url.pathname,
141
+ method: "POST",
142
+ headers: {
143
+ "Content-Type": "application/json",
144
+ "Content-Encoding": "gzip",
145
+ Authorization: `Bearer ${this._apiKey}`,
146
+ "Content-Length": compressed.length
147
+ },
148
+ timeout: 5e3
149
+ },
150
+ (res) => {
151
+ res.resume();
152
+ if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
153
+ resolve();
154
+ } else {
155
+ reject(new Error(`HTTP ${res.statusCode}`));
156
+ }
157
+ }
158
+ );
159
+ req.on("error", reject);
160
+ req.on("timeout", () => {
161
+ req.destroy();
162
+ reject(new Error("Request timeout"));
163
+ });
164
+ req.write(compressed);
165
+ req.end();
166
+ });
167
+ }
168
+ };
169
+ function sleep(ms) {
170
+ return new Promise((resolve) => setTimeout(resolve, ms));
171
+ }
172
+
173
+ // src/context.ts
174
+ import { AsyncLocalStorage } from "async_hooks";
175
+ import { randomUUID } from "crypto";
176
+
177
+ // src/agent-detect.ts
178
+ var AGENT_ENV_MAP = [
179
+ { envKey: "ARDEA_AGENT_NAME", name: "", useValue: true },
180
+ { envKey: "CANARY_AGENT_NAME", name: "", useValue: true },
181
+ { envKey: "CLAUDE_CODE", name: "claude_code" },
182
+ { envKey: "CURSOR_TRACE_ID", name: "cursor" },
183
+ { envKey: "WINDSURF_SESSION_ID", name: "windsurf" },
184
+ { envKey: "CLINE_TASK_ID", name: "cline" },
185
+ { envKey: "AIDER_VERSION", name: "aider", versionKey: "AIDER_VERSION" },
186
+ { envKey: "DEVIN_SESSION_ID", name: "devin" },
187
+ { envKey: "CODEX_CLI", name: "codex" },
188
+ { envKey: "GITHUB_COPILOT", name: "github_copilot" },
189
+ { envKey: "CODY_SESSION_ID", name: "sourcegraph_cody" }
190
+ ];
191
+ var cachedInfo = null;
192
+ function detectAgent() {
193
+ if (cachedInfo) return cachedInfo;
194
+ for (const entry of AGENT_ENV_MAP) {
195
+ const val = process.env[entry.envKey];
196
+ if (val !== void 0) {
197
+ cachedInfo = {
198
+ agent_name: entry.useValue ? val || null : entry.name,
199
+ agent_version: entry.versionKey ? process.env[entry.versionKey] ?? null : null
200
+ };
201
+ if (cachedInfo.agent_name) return cachedInfo;
202
+ }
203
+ }
204
+ cachedInfo = { agent_name: null, agent_version: null };
205
+ return cachedInfo;
206
+ }
207
+ function resetAgentCache() {
208
+ cachedInfo = null;
209
+ }
210
+
211
+ // src/context.ts
212
+ var sessionStorage = new AsyncLocalStorage();
213
+ var sdkInstanceId = randomUUID().replace(/-/g, "");
214
+ var callSequence = 0;
215
+ var defaultContext = {
216
+ frameworkSessionId: void 0
217
+ };
218
+ function getMutableContext() {
219
+ return sessionStorage.getStore() ?? defaultContext;
220
+ }
221
+ function nextCallSequence() {
222
+ callSequence += 1;
223
+ return callSequence;
224
+ }
225
+ function getBaseSignals() {
226
+ const ctx = getMutableContext();
227
+ const configuredParentProcessId = Number.parseInt(
228
+ process.env.ARDEA_PARENT_PROCESS_ID || process.env.CANARY_PARENT_PROCESS_ID || "",
229
+ 10
230
+ );
231
+ const configuredProcessKind = process.env.ARDEA_PROCESS_KIND || process.env.CANARY_PROCESS_KIND || "agent";
232
+ const configuredProcessLabel = process.env.ARDEA_PROCESS_LABEL || process.env.CANARY_PROCESS_LABEL || void 0;
233
+ const signals = {
234
+ sdk_instance_id: sdkInstanceId,
235
+ process_id: process.pid,
236
+ thread_id: 0
237
+ };
238
+ if (Number.isFinite(configuredParentProcessId)) {
239
+ signals.parent_process_id = configuredParentProcessId;
240
+ }
241
+ if (configuredProcessKind) {
242
+ signals.process_kind = configuredProcessKind;
243
+ }
244
+ if (configuredProcessLabel) {
245
+ signals.process_label = configuredProcessLabel;
246
+ }
247
+ if (ctx.frameworkSessionId) {
248
+ signals.framework_session_id = ctx.frameworkSessionId;
249
+ }
250
+ const agentInfo = detectAgent();
251
+ if (agentInfo.agent_name) {
252
+ signals.agent_name = agentInfo.agent_name;
253
+ }
254
+ if (agentInfo.agent_version) {
255
+ signals.agent_version = agentInfo.agent_version;
256
+ }
257
+ return signals;
258
+ }
259
+ function getSessionSignals() {
260
+ return {
261
+ ...getBaseSignals(),
262
+ call_sequence: nextCallSequence()
263
+ };
264
+ }
265
+ function setFrameworkSession(sessionId) {
266
+ getMutableContext().frameworkSessionId = sessionId;
267
+ }
268
+ function clearFrameworkSession() {
269
+ getMutableContext().frameworkSessionId = void 0;
270
+ }
271
+ function runWithSession(sessionId, fn) {
272
+ return sessionStorage.run(
273
+ {
274
+ frameworkSessionId: sessionId
275
+ },
276
+ fn
277
+ );
278
+ }
279
+
280
+ // src/voucher.ts
281
+ import { readFileSync, writeFileSync, mkdirSync } from "fs";
282
+ import { join } from "path";
283
+ import { homedir } from "os";
284
+ function getRedeemedPath() {
285
+ const dir = join(homedir(), ".ardea");
286
+ mkdirSync(dir, { recursive: true });
287
+ return join(dir, "redeemed-vouchers.json");
288
+ }
289
+ function getRedeemedSet() {
290
+ try {
291
+ const data = JSON.parse(readFileSync(getRedeemedPath(), "utf8"));
292
+ return new Set(Array.isArray(data) ? data : []);
293
+ } catch {
294
+ return /* @__PURE__ */ new Set();
295
+ }
296
+ }
297
+ function markRedeemed(code) {
298
+ const set = getRedeemedSet();
299
+ set.add(code);
300
+ writeFileSync(getRedeemedPath(), JSON.stringify([...set]));
301
+ }
302
+ function autoRedeemVoucher(apiKey, endpoint, code) {
303
+ if (!code.startsWith("CVR-")) return;
304
+ const redeemed = getRedeemedSet();
305
+ if (redeemed.has(code)) return;
306
+ fetch(`${endpoint}/v1/vouchers/redeem`, {
307
+ method: "POST",
308
+ headers: {
309
+ "Content-Type": "application/json",
310
+ Authorization: `Bearer ${apiKey}`
311
+ },
312
+ body: JSON.stringify({ code })
313
+ }).then(async (res) => {
314
+ if (res.ok) {
315
+ const data = await res.json();
316
+ markRedeemed(code);
317
+ console.error(
318
+ `[ardea] Voucher redeemed: +${data.credits ?? 0} ${data.vendor ?? "unknown"} credits`
319
+ );
320
+ } else if (res.status === 410) {
321
+ markRedeemed(code);
322
+ }
323
+ }).catch(() => {
324
+ });
325
+ }
326
+
327
+ // src/index.ts
328
+ var _initialized = false;
329
+ var _buffer = null;
330
+ function init(config) {
331
+ if (_initialized) return;
332
+ if (!config.apiKey.startsWith("cnry_sk_")) {
333
+ throw new Error(
334
+ "Invalid API key: must start with 'cnry_sk_'. Get your key at https://app.canary.dev"
335
+ );
336
+ }
337
+ const endpoint = config.endpoint ?? DEFAULT_ENDPOINT;
338
+ const autoFlush = config.autoFlush ?? true;
339
+ _buffer = new EventBuffer(config.apiKey, endpoint, autoFlush);
340
+ _initialized = true;
341
+ const voucherCode = process.env.ARDEA_VOUCHER || process.env.CANARY_VOUCHER;
342
+ if (voucherCode) {
343
+ autoRedeemVoucher(config.apiKey, endpoint, voucherCode);
344
+ }
345
+ }
346
+ async function shutdown() {
347
+ if (!_initialized || !_buffer) return;
348
+ await _buffer.shutdown();
349
+ _buffer = null;
350
+ _initialized = false;
351
+ }
352
+ function survey(options) {
353
+ if (!_buffer) {
354
+ throw new Error("Canary SDK not initialized. Call init() first.");
355
+ }
356
+ const signals = getSessionSignals();
357
+ const event = {
358
+ event_type: "feedback",
359
+ source_plane: "declared",
360
+ capture_mode: "declared",
361
+ source: "manual",
362
+ worked: options.worked ?? true,
363
+ context: options.context ?? "",
364
+ ts: Date.now() / 1e3,
365
+ agent_name: signals.agent_name ?? void 0,
366
+ agent_version: signals.agent_version ?? void 0,
367
+ framework_session_id: options.sessionId ?? signals.framework_session_id ?? void 0
368
+ };
369
+ if (options.frictionPoints && options.frictionPoints.length > 0) {
370
+ event.friction_points = options.frictionPoints;
371
+ }
372
+ if (options.provider) {
373
+ event.provider = options.provider;
374
+ }
375
+ _buffer.push(event);
376
+ }
377
+ function getBuffer() {
378
+ return _buffer;
379
+ }
380
+ var Ardea = { init, shutdown, survey, getBuffer };
381
+ var Canary = Ardea;
382
+ export {
383
+ Ardea,
384
+ Canary,
385
+ clearFrameworkSession,
386
+ detectAgent,
387
+ getBuffer,
388
+ getSessionSignals,
389
+ init,
390
+ resetAgentCache,
391
+ runWithSession,
392
+ setFrameworkSession,
393
+ shutdown,
394
+ survey
395
+ };
396
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/buffer.ts","../src/context.ts","../src/agent-detect.ts","../src/voucher.ts","../src/index.ts"],"sourcesContent":["/**\n * Event buffer with ring buffer, gzip flush, retry, and session cache.\n *\n * Mirrors sdk/src/ardea/buffer.py.\n */\n\nimport { gzipSync } from \"node:zlib\";\nimport https from \"node:https\";\nimport http from \"node:http\";\nimport type { CanaryEvent } from \"./types.js\";\nimport { DEFAULT_ENDPOINT } from \"./constants.js\";\n\nconst MAX_LEN = 10_000;\nconst BATCH_SIZE = 100;\nconst FLUSH_INTERVAL_MS = 10_000;\nconst MAX_EVENTS_PER_SESSION = 500;\nconst MAX_SESSION_CACHE_EVENTS = 5_000;\nconst RETRY_ATTEMPTS = 3;\nconst RETRY_BACKOFF_MS = [1000, 2000, 4000];\n\nexport class EventBuffer {\n private _buffer: CanaryEvent[] = [];\n private _sessionCache: Map<string, CanaryEvent[]> = new Map();\n private _sessionCacheEventCount = 0;\n private _apiKey: string;\n private _endpoint: string;\n private _timer: ReturnType<typeof setInterval> | null = null;\n private _stopped = false;\n /** Reference to the original (unpatched) https.request for anti-recursion. */\n private _originalHttpsRequest: typeof https.request;\n /** Reference to the original (unpatched) http.request for anti-recursion. */\n private _originalHttpRequest: typeof http.request;\n\n constructor(\n apiKey: string,\n endpoint = DEFAULT_ENDPOINT,\n autoFlush = true,\n originalHttpsRequest?: typeof https.request,\n originalHttpRequest?: typeof http.request\n ) {\n this._apiKey = apiKey;\n this._endpoint = endpoint.replace(/\\/+$/, \"\");\n this._originalHttpsRequest = originalHttpsRequest ?? https.request;\n this._originalHttpRequest = originalHttpRequest ?? http.request;\n\n if (autoFlush) {\n this._timer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS);\n this._timer.unref();\n }\n }\n\n /** Add an event to the ring buffer. */\n push(event: CanaryEvent): void {\n if (this._buffer.length >= MAX_LEN) {\n this._buffer.shift(); // drop oldest\n }\n this._buffer.push(event);\n\n // Retain in session cache\n const sessionId = (event as unknown as Record<string, unknown>).framework_session_id;\n if (typeof sessionId === \"string\" && sessionId) {\n if (this._sessionCacheEventCount < MAX_SESSION_CACHE_EVENTS) {\n let sessionEvents = this._sessionCache.get(sessionId);\n if (!sessionEvents) {\n sessionEvents = [];\n this._sessionCache.set(sessionId, sessionEvents);\n }\n if (sessionEvents.length < MAX_EVENTS_PER_SESSION) {\n sessionEvents.push(event);\n this._sessionCacheEventCount++;\n }\n }\n }\n }\n\n /** Return a copy of events for the given session from the cache. */\n getSessionEvents(sessionId: string): CanaryEvent[] {\n return [...(this._sessionCache.get(sessionId) ?? [])];\n }\n\n /** Remove session events from the cache. */\n clearSession(sessionId: string): void {\n const events = this._sessionCache.get(sessionId);\n if (events) {\n this._sessionCacheEventCount = Math.max(\n 0,\n this._sessionCacheEventCount - events.length\n );\n this._sessionCache.delete(sessionId);\n }\n }\n\n /** Remove and return up to `count` events from the buffer. */\n drain(count: number): CanaryEvent[] {\n const n = Math.min(count, this._buffer.length);\n return this._buffer.splice(0, n);\n }\n\n /** Get all buffered events (without draining). For reporter access. */\n peek(): CanaryEvent[] {\n return [...this._buffer];\n }\n\n /** Number of buffered events. */\n get length(): number {\n return this._buffer.length;\n }\n\n /** Drain a batch and send to backend. */\n async flush(): Promise<void> {\n const events = this.drain(BATCH_SIZE);\n if (events.length === 0) return;\n\n for (let attempt = 0; attempt < RETRY_ATTEMPTS; attempt++) {\n try {\n await this._send(events);\n return;\n } catch {\n if (attempt < RETRY_ATTEMPTS - 1) {\n await sleep(RETRY_BACKOFF_MS[attempt]);\n }\n }\n }\n // All retries failed — events are lost (no disk persistence in Node SDK v1)\n }\n\n /** Stop flush timer and drain all remaining events. */\n async shutdown(): Promise<void> {\n this._stopped = true;\n if (this._timer) {\n clearInterval(this._timer);\n this._timer = null;\n }\n\n while (this._buffer.length > 0) {\n const events = this.drain(BATCH_SIZE);\n if (events.length === 0) break;\n\n for (let attempt = 0; attempt < RETRY_ATTEMPTS; attempt++) {\n try {\n await this._send(events);\n break;\n } catch {\n if (attempt === RETRY_ATTEMPTS - 1) {\n // Final failure — events lost\n } else {\n await sleep(RETRY_BACKOFF_MS[attempt]);\n }\n }\n }\n }\n }\n\n /** POST events to the backend with gzip compression. */\n private _send(events: CanaryEvent[]): Promise<void> {\n const payload = JSON.stringify({\n events,\n sdk_version: \"0.1.0\",\n });\n const compressed = gzipSync(Buffer.from(payload, \"utf-8\"));\n\n const url = new URL(`${this._endpoint}/v1/events`);\n const isHttps = url.protocol === \"https:\";\n const requestFn = isHttps ? this._originalHttpsRequest : this._originalHttpRequest;\n\n return new Promise<void>((resolve, reject) => {\n const req = requestFn(\n {\n hostname: url.hostname,\n port: url.port || (isHttps ? 443 : 80),\n path: url.pathname,\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"Content-Encoding\": \"gzip\",\n Authorization: `Bearer ${this._apiKey}`,\n \"Content-Length\": compressed.length,\n },\n timeout: 5000,\n },\n (res) => {\n // Consume the response\n res.resume();\n if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {\n resolve();\n } else {\n reject(new Error(`HTTP ${res.statusCode}`));\n }\n }\n );\n\n req.on(\"error\", reject);\n req.on(\"timeout\", () => {\n req.destroy();\n reject(new Error(\"Request timeout\"));\n });\n req.write(compressed);\n req.end();\n });\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","/**\n * Session correlation via AsyncLocalStorage.\n *\n * Node.js equivalent of sdk/src/canary/context.py using\n * AsyncLocalStorage instead of ContextVar.\n */\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport { randomUUID } from \"node:crypto\";\nimport { detectAgent } from \"./agent-detect.js\";\n\ninterface SessionContext {\n frameworkSessionId?: string;\n}\n\nconst sessionStorage = new AsyncLocalStorage<SessionContext>();\nconst sdkInstanceId = randomUUID().replace(/-/g, \"\");\nlet callSequence = 0;\nconst defaultContext: SessionContext = {\n frameworkSessionId: undefined,\n};\n\nfunction getMutableContext(): SessionContext {\n return sessionStorage.getStore() ?? defaultContext;\n}\n\nfunction nextCallSequence(): number {\n callSequence += 1;\n return callSequence;\n}\n\nexport interface SessionSignals {\n sdk_instance_id: string;\n process_id: number;\n parent_process_id?: number;\n process_kind?: string;\n process_label?: string;\n thread_id: number;\n call_sequence: number;\n framework_session_id?: string;\n agent_name?: string;\n agent_version?: string;\n}\n\nfunction getBaseSignals(): Omit<SessionSignals, \"call_sequence\"> {\n const ctx = getMutableContext();\n const configuredParentProcessId = Number.parseInt(\n process.env.ARDEA_PARENT_PROCESS_ID || process.env.CANARY_PARENT_PROCESS_ID || \"\",\n 10\n );\n const configuredProcessKind = process.env.ARDEA_PROCESS_KIND || process.env.CANARY_PROCESS_KIND || \"agent\";\n const configuredProcessLabel = process.env.ARDEA_PROCESS_LABEL || process.env.CANARY_PROCESS_LABEL || undefined;\n const signals: Omit<SessionSignals, \"call_sequence\"> = {\n sdk_instance_id: sdkInstanceId,\n process_id: process.pid,\n thread_id: 0,\n };\n\n if (Number.isFinite(configuredParentProcessId)) {\n signals.parent_process_id = configuredParentProcessId;\n }\n if (configuredProcessKind) {\n signals.process_kind = configuredProcessKind;\n }\n if (configuredProcessLabel) {\n signals.process_label = configuredProcessLabel;\n }\n\n if (ctx.frameworkSessionId) {\n signals.framework_session_id = ctx.frameworkSessionId;\n }\n\n const agentInfo = detectAgent();\n if (agentInfo.agent_name) {\n signals.agent_name = agentInfo.agent_name;\n }\n if (agentInfo.agent_version) {\n signals.agent_version = agentInfo.agent_version;\n }\n\n return signals;\n}\n\n/**\n * Return session correlation signals for the current execution context.\n */\nexport function getSessionSignals(): SessionSignals {\n return {\n ...getBaseSignals(),\n call_sequence: nextCallSequence(),\n };\n}\n\n/**\n * Set the framework session ID for the current async context.\n *\n * If no async context is active, this becomes the default session ID used\n * by subsequent feedback/reporting calls on this process.\n */\nexport function setFrameworkSession(sessionId: string): void {\n getMutableContext().frameworkSessionId = sessionId;\n}\n\n/**\n * Clear the framework session ID for the current async context.\n */\nexport function clearFrameworkSession(): void {\n getMutableContext().frameworkSessionId = undefined;\n}\n\n/**\n * Run a function with a scoped framework session ID.\n *\n * The session ID propagates through async call chains automatically.\n */\nexport function runWithSession<T>(sessionId: string, fn: () => T): T {\n return sessionStorage.run(\n {\n frameworkSessionId: sessionId,\n },\n fn\n );\n}\n\n/**\n * Reset all context state. For testing only.\n */\nexport function resetContext(): void {\n callSequence = 0;\n defaultContext.frameworkSessionId = undefined;\n}\n","/**\n * Detect which AI agent framework is running via environment variables.\n */\n\nexport interface AgentInfo {\n agent_name: string | null;\n agent_version: string | null;\n}\n\nexport const AGENT_ENV_MAP: ReadonlyArray<{ envKey: string; name: string; versionKey?: string; useValue?: boolean }> = [\n { envKey: \"ARDEA_AGENT_NAME\", name: \"\", useValue: true },\n { envKey: \"CANARY_AGENT_NAME\", name: \"\", useValue: true },\n { envKey: \"CLAUDE_CODE\", name: \"claude_code\" },\n { envKey: \"CURSOR_TRACE_ID\", name: \"cursor\" },\n { envKey: \"WINDSURF_SESSION_ID\", name: \"windsurf\" },\n { envKey: \"CLINE_TASK_ID\", name: \"cline\" },\n { envKey: \"AIDER_VERSION\", name: \"aider\", versionKey: \"AIDER_VERSION\" },\n { envKey: \"DEVIN_SESSION_ID\", name: \"devin\" },\n { envKey: \"CODEX_CLI\", name: \"codex\" },\n { envKey: \"GITHUB_COPILOT\", name: \"github_copilot\" },\n { envKey: \"CODY_SESSION_ID\", name: \"sourcegraph_cody\" },\n];\n\nlet cachedInfo: AgentInfo | null = null;\n\nexport function detectAgent(): AgentInfo {\n if (cachedInfo) return cachedInfo;\n\n for (const entry of AGENT_ENV_MAP) {\n const val = process.env[entry.envKey];\n // Check !== undefined so empty strings (presence flags) still detect\n if (val !== undefined) {\n cachedInfo = {\n agent_name: entry.useValue ? (val || null) : entry.name,\n agent_version: entry.versionKey ? (process.env[entry.versionKey] ?? null) : null,\n };\n if (cachedInfo.agent_name) return cachedInfo;\n }\n }\n\n cachedInfo = { agent_name: null, agent_version: null };\n return cachedInfo;\n}\n\nexport function resetAgentCache(): void {\n cachedInfo = null;\n}\n","/**\n * Auto-redeem a voucher code on SDK init.\n * Non-blocking — fires in the background, never delays SDK startup.\n * Tracks redeemed codes in ~/.ardea/redeemed-vouchers.json.\n */\n\nimport { readFileSync, writeFileSync, mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\ntype VoucherRedeemResponse = {\n credits?: number;\n vendor?: string;\n};\n\nfunction getRedeemedPath(): string {\n const dir = join(homedir(), \".ardea\");\n mkdirSync(dir, { recursive: true });\n return join(dir, \"redeemed-vouchers.json\");\n}\n\nfunction getRedeemedSet(): Set<string> {\n try {\n const data = JSON.parse(readFileSync(getRedeemedPath(), \"utf8\"));\n return new Set(Array.isArray(data) ? data : []);\n } catch {\n return new Set();\n }\n}\n\nfunction markRedeemed(code: string): void {\n const set = getRedeemedSet();\n set.add(code);\n writeFileSync(getRedeemedPath(), JSON.stringify([...set]));\n}\n\nexport function autoRedeemVoucher(\n apiKey: string,\n endpoint: string,\n code: string,\n): void {\n if (!code.startsWith(\"CVR-\")) return;\n\n const redeemed = getRedeemedSet();\n if (redeemed.has(code)) return;\n\n // Fire-and-forget — don't block SDK init\n fetch(`${endpoint}/v1/vouchers/redeem`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify({ code }),\n })\n .then(async (res) => {\n if (res.ok) {\n const data = (await res.json()) as VoucherRedeemResponse;\n markRedeemed(code);\n // Log to stderr to avoid corrupting any MCP stdio\n console.error(\n `[ardea] Voucher redeemed: +${data.credits ?? 0} ${data.vendor ?? \"unknown\"} credits`,\n );\n } else if (res.status === 410) {\n // Already redeemed, expired, or revoked — mark locally to avoid retries\n markRedeemed(code);\n }\n })\n .catch(() => {\n // Silently fail — SDK init must not be affected\n });\n}\n","/**\n * ardea — MCP tools for AI agents to report API experiences.\n */\n\nimport { EventBuffer } from \"./buffer.js\";\nimport {\n runWithSession,\n setFrameworkSession,\n clearFrameworkSession,\n getSessionSignals,\n} from \"./context.js\";\nimport type { CanaryConfig, CanaryEvent, FeedbackEvent } from \"./types.js\";\nimport { DEFAULT_ENDPOINT } from \"./constants.js\";\nimport { autoRedeemVoucher } from \"./voucher.js\";\n\nlet _initialized = false;\nlet _buffer: EventBuffer | null = null;\n\n/**\n * Initialize Canary SDK for manual feedback submission.\n */\nexport function init(config: CanaryConfig): void {\n if (_initialized) return;\n\n if (!config.apiKey.startsWith(\"cnry_sk_\")) {\n throw new Error(\n \"Invalid API key: must start with 'cnry_sk_'. \" +\n \"Get your key at https://app.canary.dev\"\n );\n }\n\n const endpoint = config.endpoint ?? DEFAULT_ENDPOINT;\n const autoFlush = config.autoFlush ?? true;\n\n _buffer = new EventBuffer(config.apiKey, endpoint, autoFlush);\n _initialized = true;\n\n // Auto-redeem voucher if ARDEA_VOUCHER or CANARY_VOUCHER env var is set (non-blocking)\n const voucherCode = process.env.ARDEA_VOUCHER || process.env.CANARY_VOUCHER;\n if (voucherCode) {\n autoRedeemVoucher(config.apiKey, endpoint, voucherCode);\n }\n}\n\n/**\n * Shut down the SDK and flush any queued feedback events.\n */\nexport async function shutdown(): Promise<void> {\n if (!_initialized || !_buffer) return;\n\n await _buffer.shutdown();\n _buffer = null;\n _initialized = false;\n}\n\n/**\n * Submit manual feedback.\n */\nexport function survey(options: {\n worked?: boolean;\n context?: string;\n frictionPoints?: string[];\n provider?: string;\n sessionId?: string;\n}): void {\n if (!_buffer) {\n throw new Error(\"Canary SDK not initialized. Call init() first.\");\n }\n\n // Pick up session context from AsyncLocalStorage (set by runWithSession)\n const signals = getSessionSignals();\n const event: FeedbackEvent = {\n event_type: \"feedback\",\n source_plane: \"declared\",\n capture_mode: \"declared\",\n source: \"manual\",\n worked: options.worked ?? true,\n context: options.context ?? \"\",\n ts: Date.now() / 1000,\n agent_name: signals.agent_name ?? undefined,\n agent_version: signals.agent_version ?? undefined,\n framework_session_id: options.sessionId ?? signals.framework_session_id ?? undefined,\n };\n\n if (options.frictionPoints && options.frictionPoints.length > 0) {\n event.friction_points = options.frictionPoints;\n }\n if (options.provider) {\n event.provider = options.provider;\n }\n\n _buffer.push(event);\n}\n\n/**\n * Get the global EventBuffer (for advanced usage / adapters).\n */\nexport function getBuffer(): EventBuffer | null {\n return _buffer;\n}\n\n// Re-export session helpers\nexport {\n runWithSession,\n setFrameworkSession,\n clearFrameworkSession,\n getSessionSignals,\n};\n\n// Re-export types\nexport type { ArdeaConfig, CanaryConfig, CanaryEvent, FeedbackEvent } from \"./types.js\";\nexport { detectAgent, resetAgentCache } from \"./agent-detect.js\";\n\n/** Alias for init — Ardea rebrand entry point. */\nexport const Ardea = { init, shutdown, survey, getBuffer };\n\n/** @deprecated Use Ardea instead. */\nexport const Canary = Ardea;\n"],"mappings":"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","names":[]}