@burn0/burn0 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.
@@ -0,0 +1,864 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") {
10
+ for (let key of __getOwnPropNames(from))
11
+ if (!__hasOwnProp.call(to, key) && key !== except)
12
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
13
+ }
14
+ return to;
15
+ };
16
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
17
+ // If the importer is in node compatibility mode or this is not an ESM
18
+ // file that has been converted to a CommonJS file using a Babel-
19
+ // compatible transform (i.e. "__esModule" has not been set), then set
20
+ // "default" to the CommonJS "module.exports" for node compatibility.
21
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
22
+ mod
23
+ ));
24
+
25
+ // src/config/env.ts
26
+ function getApiKey() {
27
+ const key = process.env.BURN0_API_KEY;
28
+ return key && key.length > 0 ? key : void 0;
29
+ }
30
+ function detectMode(opts) {
31
+ const isTest = process.env.NODE_ENV === "test";
32
+ if (isTest) {
33
+ return process.env.BURN0_ENABLE_TEST === "1" ? "test-enabled" : "test-disabled";
34
+ }
35
+ if (opts.apiKey) {
36
+ return opts.isTTY ? "dev-cloud" : "prod-cloud";
37
+ }
38
+ return opts.isTTY ? "dev-local" : "prod-local";
39
+ }
40
+ function isTTY() {
41
+ return Boolean(process.stdout.isTTY);
42
+ }
43
+ function isDebug() {
44
+ return process.env.BURN0_DEBUG === "1" || process.env.BURN0_DEBUG === "true";
45
+ }
46
+
47
+ // src/interceptor/guard.ts
48
+ var patched = false;
49
+ function canPatch() {
50
+ return !patched;
51
+ }
52
+ function markPatched() {
53
+ patched = true;
54
+ }
55
+ function resetGuard() {
56
+ patched = false;
57
+ }
58
+ var KNOWN_SDK_MODULES = [
59
+ "openai",
60
+ "@anthropic-ai/sdk",
61
+ "@google/generative-ai",
62
+ "@mistralai/mistralai",
63
+ "cohere-ai",
64
+ "stripe",
65
+ "@sendgrid/mail",
66
+ "twilio",
67
+ "resend",
68
+ "@supabase/supabase-js"
69
+ ];
70
+ function checkImportOrder() {
71
+ const preloaded2 = [];
72
+ if (typeof require !== "undefined" && require.cache) {
73
+ for (const sdk of KNOWN_SDK_MODULES) {
74
+ if (Object.keys(require.cache).some((k) => k.includes(`/node_modules/${sdk}/`))) {
75
+ preloaded2.push(sdk);
76
+ }
77
+ }
78
+ }
79
+ return preloaded2;
80
+ }
81
+
82
+ // src/services/map.ts
83
+ var BUILT_IN_MAP = {
84
+ // LLMs
85
+ "api.openai.com": "openai",
86
+ "api.anthropic.com": "anthropic",
87
+ "generativelanguage.googleapis.com": "google-gemini",
88
+ "api.mistral.ai": "mistral",
89
+ "api.cohere.com": "cohere",
90
+ "api.groq.com": "groq",
91
+ "api.together.xyz": "together-ai",
92
+ "api.perplexity.ai": "perplexity",
93
+ "api.fireworks.ai": "fireworks-ai",
94
+ "api.deepseek.com": "deepseek",
95
+ "api.replicate.com": "replicate",
96
+ "api.ai21.com": "ai21",
97
+ // APIs
98
+ "api.stripe.com": "stripe",
99
+ "api.paypal.com": "paypal",
100
+ "api.sendgrid.com": "sendgrid",
101
+ "api.resend.com": "resend",
102
+ "api.postmarkapp.com": "postmark",
103
+ "api.mailgun.net": "mailgun",
104
+ "api.twilio.com": "twilio",
105
+ "api.vonage.com": "vonage",
106
+ "api.clerk.com": "clerk",
107
+ "api.cloudinary.com": "cloudinary",
108
+ "upload.uploadcare.com": "uploadcare",
109
+ "maps.googleapis.com": "google-maps",
110
+ "api.mapbox.com": "mapbox",
111
+ "api.segment.io": "segment",
112
+ "api.mixpanel.com": "mixpanel",
113
+ "api.github.com": "github-api",
114
+ "api.plaid.com": "plaid",
115
+ "api.pinecone.io": "pinecone",
116
+ "supabase.co": "supabase"
117
+ };
118
+ var IGNORED_PATTERNS = ["localhost", "127.0.0.1", "0.0.0.0", "[::1]"];
119
+ var serviceMap = { ...BUILT_IN_MAP };
120
+ function identifyService(hostname) {
121
+ if (IGNORED_PATTERNS.includes(hostname) || hostname.startsWith("localhost:")) {
122
+ return null;
123
+ }
124
+ if (serviceMap[hostname]) return serviceMap[hostname];
125
+ for (const [mapHost, service] of Object.entries(serviceMap)) {
126
+ if (hostname.endsWith("." + mapHost)) return service;
127
+ }
128
+ return `unknown:${hostname}`;
129
+ }
130
+
131
+ // src/types.ts
132
+ var SCHEMA_VERSION = 1;
133
+
134
+ // src/interceptor/stream.ts
135
+ function teeReadableStream(stream) {
136
+ return stream.tee();
137
+ }
138
+ function extractUsageFromSSE(raw) {
139
+ const lines = raw.split("\n");
140
+ for (let i = lines.length - 1; i >= 0; i--) {
141
+ const line = lines[i];
142
+ if (!line.startsWith("data: ") || line === "data: [DONE]") continue;
143
+ try {
144
+ const parsed = JSON.parse(line.slice(6));
145
+ if (parsed.usage) return parsed.usage;
146
+ } catch {
147
+ }
148
+ }
149
+ return null;
150
+ }
151
+ async function collectStream(stream, timeoutMs = 3e4) {
152
+ const reader = stream.getReader();
153
+ const chunks = [];
154
+ const decoder = new TextDecoder();
155
+ const timeout = new Promise((_, reject) => {
156
+ setTimeout(() => reject(new Error("stream timeout")), timeoutMs);
157
+ });
158
+ try {
159
+ while (true) {
160
+ const result = await Promise.race([reader.read(), timeout]);
161
+ if (result.done) break;
162
+ chunks.push(decoder.decode(result.value, { stream: true }));
163
+ }
164
+ } catch {
165
+ } finally {
166
+ try {
167
+ reader.releaseLock();
168
+ } catch {
169
+ }
170
+ }
171
+ return chunks.join("");
172
+ }
173
+
174
+ // src/interceptor/fetch.ts
175
+ var originalFetch = null;
176
+ function patchFetch(onEvent) {
177
+ originalFetch = globalThis.fetch;
178
+ globalThis.fetch = async function burn0Fetch(input, init) {
179
+ const startTime = Date.now();
180
+ const url = new URL(typeof input === "string" ? input : input instanceof URL ? input.href : input.url);
181
+ const hostname = url.hostname;
182
+ const service = identifyService(hostname);
183
+ if (!service) {
184
+ return originalFetch(input, init);
185
+ }
186
+ let model;
187
+ if (init?.body && typeof init.body === "string") {
188
+ try {
189
+ const parsed = JSON.parse(init.body);
190
+ if (parsed.model) model = parsed.model;
191
+ } catch {
192
+ }
193
+ }
194
+ if (!model) {
195
+ const modelMatch = url.pathname.match(/\/models\/([^/:]+)/);
196
+ if (modelMatch) model = modelMatch[1];
197
+ }
198
+ const response = await originalFetch(input, init);
199
+ const duration = Date.now() - startTime;
200
+ let tokensIn;
201
+ let tokensOut;
202
+ if (response.headers.get("content-type")?.includes("text/event-stream") && response.body) {
203
+ const [forCaller, forBurn0] = teeReadableStream(response.body);
204
+ collectStream(forBurn0).then((raw) => {
205
+ const usage = extractUsageFromSSE(raw);
206
+ let sseTokensIn = usage?.prompt_tokens ?? usage?.input_tokens;
207
+ let sseTokensOut = usage?.completion_tokens ?? usage?.output_tokens;
208
+ if (sseTokensIn !== void 0 && sseTokensIn < 0) sseTokensIn = void 0;
209
+ if (sseTokensOut !== void 0 && sseTokensOut < 0) sseTokensOut = void 0;
210
+ const sseEvent = {
211
+ schema_version: SCHEMA_VERSION,
212
+ service,
213
+ endpoint: url.pathname,
214
+ model,
215
+ tokens_in: sseTokensIn,
216
+ tokens_out: sseTokensOut,
217
+ status_code: response.status,
218
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
219
+ duration_ms: duration,
220
+ estimated: !usage
221
+ };
222
+ try {
223
+ onEvent(sseEvent);
224
+ } catch {
225
+ }
226
+ }).catch(() => {
227
+ const sseEvent = {
228
+ schema_version: SCHEMA_VERSION,
229
+ service,
230
+ endpoint: url.pathname,
231
+ model,
232
+ status_code: response.status,
233
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
234
+ duration_ms: duration,
235
+ estimated: true
236
+ };
237
+ try {
238
+ onEvent(sseEvent);
239
+ } catch {
240
+ }
241
+ });
242
+ return new Response(forCaller, {
243
+ status: response.status,
244
+ statusText: response.statusText,
245
+ headers: response.headers
246
+ });
247
+ }
248
+ if (response.headers.get("content-type")?.includes("application/json")) {
249
+ try {
250
+ const cloned = response.clone();
251
+ const body = await cloned.json();
252
+ if (body.usage) {
253
+ tokensIn = body.usage.prompt_tokens ?? body.usage.input_tokens;
254
+ tokensOut = body.usage.completion_tokens ?? body.usage.output_tokens;
255
+ }
256
+ if (body.usageMetadata) {
257
+ tokensIn = body.usageMetadata.promptTokenCount;
258
+ tokensOut = body.usageMetadata.candidatesTokenCount;
259
+ }
260
+ if (body.model) model = body.model;
261
+ } catch {
262
+ }
263
+ }
264
+ if (tokensIn !== void 0 && tokensIn < 0) tokensIn = void 0;
265
+ if (tokensOut !== void 0 && tokensOut < 0) tokensOut = void 0;
266
+ const event = {
267
+ schema_version: SCHEMA_VERSION,
268
+ service,
269
+ endpoint: url.pathname,
270
+ model,
271
+ tokens_in: tokensIn,
272
+ tokens_out: tokensOut,
273
+ status_code: response.status,
274
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
275
+ duration_ms: duration,
276
+ estimated: false
277
+ };
278
+ try {
279
+ onEvent(event);
280
+ } catch {
281
+ }
282
+ return response;
283
+ };
284
+ }
285
+ function unpatchFetch() {
286
+ if (originalFetch) {
287
+ globalThis.fetch = originalFetch;
288
+ originalFetch = null;
289
+ }
290
+ }
291
+
292
+ // src/interceptor/http.ts
293
+ var import_node_http = __toESM(require("http"));
294
+ var import_node_https = __toESM(require("https"));
295
+ var originalHttpRequest = null;
296
+ var originalHttpsRequest = null;
297
+ var originalHttpGet = null;
298
+ var originalHttpsGet = null;
299
+ function wrapRequest(original, onEvent) {
300
+ return function burn0Request(...args) {
301
+ const req = original.apply(this, args);
302
+ const startTime = Date.now();
303
+ const firstArg = args[0];
304
+ let hostname = "";
305
+ let endpoint = "/";
306
+ if (typeof firstArg === "string") {
307
+ try {
308
+ const parsed = new URL(firstArg);
309
+ hostname = parsed.hostname;
310
+ endpoint = parsed.pathname;
311
+ } catch {
312
+ }
313
+ } else if (firstArg instanceof URL) {
314
+ hostname = firstArg.hostname;
315
+ endpoint = firstArg.pathname;
316
+ } else if (firstArg && typeof firstArg === "object") {
317
+ hostname = firstArg.hostname ?? firstArg.host ?? "";
318
+ endpoint = firstArg.path ?? "/";
319
+ }
320
+ const cleanHostname = hostname.replace(/:\d+$/, "");
321
+ const service = identifyService(cleanHostname);
322
+ if (!service) return req;
323
+ req.on("response", (res) => {
324
+ const isJson = res.headers["content-type"]?.includes("application/json");
325
+ const chunks = [];
326
+ const MAX_RESPONSE_SIZE = 5 * 1024 * 1024;
327
+ let totalSize = 0;
328
+ let tooLarge = false;
329
+ if (isJson) {
330
+ res.on("data", (chunk) => {
331
+ totalSize += chunk.length;
332
+ if (totalSize > MAX_RESPONSE_SIZE) {
333
+ tooLarge = true;
334
+ return;
335
+ }
336
+ chunks.push(chunk);
337
+ });
338
+ }
339
+ res.on("end", () => {
340
+ const duration = Date.now() - startTime;
341
+ let tokensIn;
342
+ let tokensOut;
343
+ let model;
344
+ if (isJson && chunks.length > 0 && !tooLarge) {
345
+ try {
346
+ const body = JSON.parse(Buffer.concat(chunks).toString());
347
+ if (body.usage) {
348
+ tokensIn = body.usage.prompt_tokens ?? body.usage.input_tokens;
349
+ tokensOut = body.usage.completion_tokens ?? body.usage.output_tokens;
350
+ }
351
+ if (body.model) model = body.model;
352
+ } catch {
353
+ }
354
+ }
355
+ if (tokensIn !== void 0 && tokensIn < 0) tokensIn = void 0;
356
+ if (tokensOut !== void 0 && tokensOut < 0) tokensOut = void 0;
357
+ const event = {
358
+ schema_version: SCHEMA_VERSION,
359
+ service,
360
+ endpoint: endpoint.toString(),
361
+ model,
362
+ tokens_in: tokensIn,
363
+ tokens_out: tokensOut,
364
+ status_code: res.statusCode ?? 0,
365
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
366
+ duration_ms: duration,
367
+ estimated: false
368
+ };
369
+ try {
370
+ onEvent(event);
371
+ } catch {
372
+ }
373
+ });
374
+ });
375
+ return req;
376
+ };
377
+ }
378
+ function patchHttp(onEvent) {
379
+ originalHttpRequest = import_node_http.default.request;
380
+ originalHttpsRequest = import_node_https.default.request;
381
+ originalHttpGet = import_node_http.default.get;
382
+ originalHttpsGet = import_node_https.default.get;
383
+ import_node_http.default.request = wrapRequest(originalHttpRequest, onEvent);
384
+ import_node_https.default.request = wrapRequest(originalHttpsRequest, onEvent);
385
+ import_node_http.default.get = wrapRequest(originalHttpGet, onEvent);
386
+ import_node_https.default.get = wrapRequest(originalHttpsGet, onEvent);
387
+ }
388
+ function unpatchHttp() {
389
+ if (originalHttpRequest) {
390
+ import_node_http.default.request = originalHttpRequest;
391
+ originalHttpRequest = null;
392
+ }
393
+ if (originalHttpsRequest) {
394
+ import_node_https.default.request = originalHttpsRequest;
395
+ originalHttpsRequest = null;
396
+ }
397
+ if (originalHttpGet) {
398
+ import_node_http.default.get = originalHttpGet;
399
+ originalHttpGet = null;
400
+ }
401
+ if (originalHttpsGet) {
402
+ import_node_https.default.get = originalHttpsGet;
403
+ originalHttpsGet = null;
404
+ }
405
+ }
406
+
407
+ // src/track.ts
408
+ var import_node_async_hooks = require("async_hooks");
409
+ var storage = new import_node_async_hooks.AsyncLocalStorage();
410
+ var activeSpan = null;
411
+ function createTracker() {
412
+ async function track2(feature, metadata, fn) {
413
+ await storage.run({ feature, metadata }, fn);
414
+ }
415
+ function startSpan2(feature, metadata) {
416
+ activeSpan = { feature, metadata };
417
+ return {
418
+ end() {
419
+ activeSpan = null;
420
+ }
421
+ };
422
+ }
423
+ function enrichEvent2(event) {
424
+ const ctx = storage.getStore() ?? activeSpan;
425
+ if (!ctx) return event;
426
+ return {
427
+ ...event,
428
+ feature: ctx.feature,
429
+ metadata: ctx.metadata && Object.keys(ctx.metadata).length > 0 ? ctx.metadata : void 0
430
+ };
431
+ }
432
+ return { track: track2, startSpan: startSpan2, enrichEvent: enrichEvent2 };
433
+ }
434
+
435
+ // src/restore.ts
436
+ function createRestorer(deps) {
437
+ return () => {
438
+ deps.unpatchFetch();
439
+ deps.unpatchHttp();
440
+ deps.resetGuard();
441
+ };
442
+ }
443
+
444
+ // src/transport/dispatcher.ts
445
+ function createDispatcher(mode2, deps) {
446
+ return (event) => {
447
+ switch (mode2) {
448
+ case "dev-local":
449
+ deps.logEvent?.(event);
450
+ deps.writeLedger?.(event);
451
+ break;
452
+ case "dev-cloud":
453
+ deps.logEvent?.(event);
454
+ deps.addToBatch?.(event);
455
+ break;
456
+ case "prod-cloud":
457
+ deps.addToBatch?.(event);
458
+ break;
459
+ case "prod-local":
460
+ deps.accumulate?.(event);
461
+ break;
462
+ case "test-enabled":
463
+ deps.logEvent?.(event);
464
+ deps.writeLedger?.(event);
465
+ deps.addToBatch?.(event);
466
+ break;
467
+ case "test-disabled":
468
+ break;
469
+ }
470
+ };
471
+ }
472
+
473
+ // src/transport/batch.ts
474
+ var BatchBuffer = class {
475
+ events = [];
476
+ timer = null;
477
+ options;
478
+ constructor(options) {
479
+ this.options = options;
480
+ this.timer = setInterval(() => {
481
+ if (this.events.length > 0) this.flush();
482
+ }, options.timeThresholdMs);
483
+ if (this.timer && typeof this.timer === "object" && "unref" in this.timer) {
484
+ this.timer.unref();
485
+ }
486
+ }
487
+ add(event) {
488
+ this.events.push(event);
489
+ if (this.events.length > this.options.maxSize) {
490
+ this.events = this.events.slice(-this.options.maxSize);
491
+ }
492
+ if (this.events.length >= this.options.sizeThreshold) {
493
+ this.flush();
494
+ }
495
+ }
496
+ flush() {
497
+ if (this.events.length === 0) return;
498
+ const batch2 = [...this.events];
499
+ this.events = [];
500
+ this.options.onFlush(batch2);
501
+ }
502
+ destroy() {
503
+ if (this.timer) {
504
+ clearInterval(this.timer);
505
+ this.timer = null;
506
+ }
507
+ }
508
+ };
509
+
510
+ // src/transport/local.ts
511
+ var import_node_fs = __toESM(require("fs"));
512
+ var import_node_path = __toESM(require("path"));
513
+ var BURN0_DIR = ".burn0";
514
+ var LEDGER_FILE = "costs.jsonl";
515
+ var MAX_FILE_SIZE = 10 * 1024 * 1024;
516
+ var MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
517
+ var LocalLedger = class {
518
+ filePath;
519
+ dirPath;
520
+ constructor(projectRoot) {
521
+ this.dirPath = import_node_path.default.join(projectRoot, BURN0_DIR);
522
+ this.filePath = import_node_path.default.join(this.dirPath, LEDGER_FILE);
523
+ }
524
+ write(event) {
525
+ this.ensureDir();
526
+ this.rotateIfNeeded();
527
+ import_node_fs.default.appendFileSync(this.filePath, JSON.stringify(event) + "\n");
528
+ }
529
+ read() {
530
+ try {
531
+ const content = import_node_fs.default.readFileSync(this.filePath, "utf-8").trim();
532
+ if (!content) return [];
533
+ return content.split("\n").map((line) => JSON.parse(line));
534
+ } catch {
535
+ return [];
536
+ }
537
+ }
538
+ ensureDir() {
539
+ if (!import_node_fs.default.existsSync(this.dirPath)) import_node_fs.default.mkdirSync(this.dirPath, { recursive: true });
540
+ }
541
+ rotateIfNeeded() {
542
+ try {
543
+ const stat = import_node_fs.default.statSync(this.filePath);
544
+ if (stat.size > MAX_FILE_SIZE) {
545
+ this.pruneOldEntries();
546
+ return;
547
+ }
548
+ } catch {
549
+ return;
550
+ }
551
+ const events = this.read();
552
+ if (events.length > 0) {
553
+ const oldest = new Date(events[0].timestamp).getTime();
554
+ if (Date.now() - oldest > MAX_AGE_MS) this.pruneOldEntries();
555
+ }
556
+ }
557
+ pruneOldEntries() {
558
+ const cutoff = Date.now() - MAX_AGE_MS;
559
+ const events = this.read().filter((e) => new Date(e.timestamp).getTime() > cutoff);
560
+ const content = events.map((e) => JSON.stringify(e)).join("\n") + (events.length ? "\n" : "");
561
+ import_node_fs.default.writeFileSync(this.filePath, content);
562
+ }
563
+ };
564
+
565
+ // src/transport/api.ts
566
+ var SDK_VERSION = "0.1.0";
567
+ async function shipEvents(events, apiKey2, baseUrl, fetchFn = globalThis.fetch) {
568
+ const maxAttempts = 3;
569
+ const baseDelayMs = 1e3;
570
+ const maxDelayMs = 5e3;
571
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
572
+ try {
573
+ const response = await fetchFn(`${baseUrl}/v1/events`, {
574
+ method: "POST",
575
+ headers: {
576
+ "Content-Type": "application/json",
577
+ "Authorization": `Bearer ${apiKey2}`,
578
+ "X-Burn0-SDK-Version": SDK_VERSION
579
+ },
580
+ body: JSON.stringify({ events, sdk_version: SDK_VERSION })
581
+ });
582
+ if (response.ok) return true;
583
+ } catch (err) {
584
+ if (isDebug()) {
585
+ console.warn("[burn0] Event shipping failed:", err.message);
586
+ }
587
+ }
588
+ if (attempt < maxAttempts - 1) {
589
+ const delay = Math.min(baseDelayMs * Math.pow(2, attempt), maxDelayMs);
590
+ await new Promise((resolve) => setTimeout(resolve, delay));
591
+ }
592
+ }
593
+ return false;
594
+ }
595
+
596
+ // src/transport/local-pricing.ts
597
+ var import_node_fs2 = __toESM(require("fs"));
598
+ var import_node_path2 = __toESM(require("path"));
599
+ var pricingData = null;
600
+ var CACHE_FILE = ".burn0/pricing-cache.json";
601
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
602
+ var FREE_SERVICES = /* @__PURE__ */ new Set([
603
+ "github-api",
604
+ "slack-api",
605
+ "discord-api"
606
+ ]);
607
+ async function fetchPricing(apiUrl, fetchFn) {
608
+ try {
609
+ const cachePath = import_node_path2.default.join(process.cwd(), CACHE_FILE);
610
+ if (import_node_fs2.default.existsSync(cachePath)) {
611
+ const raw = import_node_fs2.default.readFileSync(cachePath, "utf-8");
612
+ const cached = JSON.parse(raw);
613
+ if (Date.now() - cached.cached_at < CACHE_TTL_MS) {
614
+ pricingData = cached;
615
+ return;
616
+ }
617
+ }
618
+ } catch {
619
+ }
620
+ try {
621
+ const response = await fetchFn(`${apiUrl}/v1/pricing`, {
622
+ headers: { "Accept": "application/json" }
623
+ });
624
+ if (response.ok) {
625
+ const data = await response.json();
626
+ pricingData = data;
627
+ try {
628
+ const dir = import_node_path2.default.join(process.cwd(), ".burn0");
629
+ if (!import_node_fs2.default.existsSync(dir)) import_node_fs2.default.mkdirSync(dir, { recursive: true });
630
+ import_node_fs2.default.writeFileSync(
631
+ import_node_path2.default.join(process.cwd(), CACHE_FILE),
632
+ JSON.stringify({ ...data, cached_at: Date.now() }, null, 2)
633
+ );
634
+ } catch {
635
+ }
636
+ }
637
+ } catch {
638
+ }
639
+ }
640
+ function estimateLocalCost(event) {
641
+ if (FREE_SERVICES.has(event.service)) {
642
+ return { type: "free" };
643
+ }
644
+ if (event.service.startsWith("unknown:")) {
645
+ return { type: "unknown" };
646
+ }
647
+ if (!pricingData) {
648
+ return { type: "loading" };
649
+ }
650
+ const svc = pricingData.services[event.service];
651
+ if (!svc) {
652
+ return { type: "unknown" };
653
+ }
654
+ if (svc.type === "llm") {
655
+ if (!event.model) return { type: "unknown" };
656
+ if (event.tokens_in === void 0 || event.tokens_out === void 0) {
657
+ return { type: "no-tokens" };
658
+ }
659
+ let prices = svc.models[event.model];
660
+ if (!prices) {
661
+ const match = Object.keys(svc.models).find((m) => event.model.startsWith(m));
662
+ if (match) prices = svc.models[match];
663
+ }
664
+ if (prices) {
665
+ const inputCost = event.tokens_in / 1e6 * prices[0];
666
+ const outputCost = event.tokens_out / 1e6 * prices[1];
667
+ return { type: "priced", cost: inputCost + outputCost };
668
+ }
669
+ return { type: "unknown" };
670
+ }
671
+ if (svc.type === "api") {
672
+ const endpoint = event.endpoint ?? "";
673
+ for (const [prefix, cost] of Object.entries(svc.endpoints)) {
674
+ if (prefix !== "*" && endpoint.startsWith(prefix)) {
675
+ return cost === 0 ? { type: "free" } : { type: "priced", cost };
676
+ }
677
+ }
678
+ const defaultCost = svc.endpoints["*"];
679
+ if (defaultCost !== void 0) {
680
+ return defaultCost === 0 ? { type: "free" } : { type: "priced", cost: defaultCost };
681
+ }
682
+ return { type: "unknown" };
683
+ }
684
+ if (svc.type === "fixed") {
685
+ return { type: "fixed-tier" };
686
+ }
687
+ return { type: "unknown" };
688
+ }
689
+
690
+ // src/transport/logger.ts
691
+ var DIM = "\x1B[2m";
692
+ var RESET = "\x1B[0m";
693
+ var CYAN = "\x1B[36m";
694
+ var GREEN = "\x1B[32m";
695
+ var YELLOW = "\x1B[33m";
696
+ var WHITE = "\x1B[37m";
697
+ var BOLD = "\x1B[1m";
698
+ var ORANGE = "\x1B[38;2;250;93;25m";
699
+ var GRAY = "\x1B[90m";
700
+ var headerPrinted = false;
701
+ var sessionTotal = 0;
702
+ var eventCount = 0;
703
+ function formatTokens(count) {
704
+ if (count >= 1e6) return `${(count / 1e6).toFixed(1)}M`;
705
+ if (count >= 1e3) return `${(count / 1e3).toFixed(1)}K`;
706
+ return count.toString();
707
+ }
708
+ function formatCost(cost) {
709
+ if (cost >= 1) return `$${cost.toFixed(2)}`;
710
+ if (cost >= 0.01) return `$${cost.toFixed(4)}`;
711
+ return `$${cost.toFixed(6)}`;
712
+ }
713
+ function formatCostEstimate(estimate) {
714
+ switch (estimate.type) {
715
+ case "priced":
716
+ return `${GREEN}${formatCost(estimate.cost)}${RESET}`;
717
+ case "free":
718
+ return `${GRAY}free${RESET}`;
719
+ case "no-tokens":
720
+ return `${YELLOW}no usage${RESET}`;
721
+ case "fixed-tier":
722
+ return `${YELLOW}plan?${RESET}`;
723
+ case "unknown":
724
+ return `${GRAY}untracked${RESET}`;
725
+ case "loading":
726
+ return `${GRAY}...${RESET}`;
727
+ }
728
+ }
729
+ function printHeader() {
730
+ if (headerPrinted) return;
731
+ headerPrinted = true;
732
+ process.stdout.write(`
733
+ `);
734
+ process.stdout.write(` ${ORANGE}${BOLD} burn0 ${RESET} ${DIM}live cost tracking${RESET}
735
+ `);
736
+ process.stdout.write(`
737
+ `);
738
+ process.stdout.write(` ${GRAY}SERVICE ENDPOINT / MODEL USAGE COST${RESET}
739
+ `);
740
+ process.stdout.write(` ${GRAY}${"\u2500".repeat(68)}${RESET}
741
+ `);
742
+ }
743
+ function printSessionTotal() {
744
+ process.stdout.write(` ${GRAY}${"\u2500".repeat(68)}${RESET}
745
+ `);
746
+ if (sessionTotal > 0) {
747
+ process.stdout.write(` ${GRAY}${eventCount} calls${RESET} ${ORANGE}${BOLD}${formatCost(sessionTotal)}${RESET}
748
+ `);
749
+ } else {
750
+ process.stdout.write(` ${GRAY}${eventCount} calls${RESET} ${GRAY}$0${RESET}
751
+ `);
752
+ }
753
+ process.stdout.write(` ${GRAY}${"\u2500".repeat(68)}${RESET}
754
+ `);
755
+ }
756
+ function formatEventLine(event) {
757
+ const service = event.service.length > 15 ? event.service.substring(0, 14) + "." : event.service;
758
+ const modelOrEndpoint = event.model ? event.model.length > 29 ? event.model.substring(0, 28) + "." : event.model : event.endpoint.length > 29 ? event.endpoint.substring(0, 28) + "." : event.endpoint;
759
+ let usage = "";
760
+ if (event.tokens_in !== void 0 && event.tokens_out !== void 0) {
761
+ usage = `${formatTokens(event.tokens_in)} \u2192 ${formatTokens(event.tokens_out)}`;
762
+ }
763
+ const estimate = estimateLocalCost(event);
764
+ const costStr = formatCostEstimate(estimate);
765
+ return ` ${CYAN}${service.padEnd(16)}${RESET} ${WHITE}${modelOrEndpoint.padEnd(30)}${RESET}${GRAY}${usage.padEnd(15)}${RESET}${costStr}`;
766
+ }
767
+ function formatProcessSummary(events, uptimeSeconds) {
768
+ const services = {};
769
+ for (const event of events) {
770
+ if (!services[event.service]) services[event.service] = { calls: 0 };
771
+ services[event.service].calls++;
772
+ if (event.tokens_in !== void 0) services[event.service].tokens_in = (services[event.service].tokens_in ?? 0) + event.tokens_in;
773
+ if (event.tokens_out !== void 0) services[event.service].tokens_out = (services[event.service].tokens_out ?? 0) + event.tokens_out;
774
+ }
775
+ for (const svc of Object.values(services)) {
776
+ if (svc.tokens_in === void 0) delete svc.tokens_in;
777
+ if (svc.tokens_out === void 0) delete svc.tokens_out;
778
+ }
779
+ return JSON.stringify({
780
+ burn0: "process-summary",
781
+ uptime_hours: +(uptimeSeconds / 3600).toFixed(1),
782
+ total_calls: events.length,
783
+ services,
784
+ message: "Add BURN0_API_KEY to see cost breakdowns \u2192 burn0.dev"
785
+ });
786
+ }
787
+ function logEvent(event) {
788
+ printHeader();
789
+ const estimate = estimateLocalCost(event);
790
+ if (estimate.type === "priced" && estimate.cost > 0) {
791
+ sessionTotal += estimate.cost;
792
+ }
793
+ eventCount++;
794
+ process.stdout.write(`${formatEventLine(event)}
795
+ `);
796
+ if (eventCount % 5 === 0) {
797
+ printSessionTotal();
798
+ }
799
+ }
800
+
801
+ // src/index.ts
802
+ var BURN0_API_URL = process.env.BURN0_API_URL ?? "https://api.burn0.dev";
803
+ var apiKey = getApiKey();
804
+ var mode = detectMode({ isTTY: isTTY(), apiKey });
805
+ var { track, startSpan, enrichEvent } = createTracker();
806
+ var originalFetch2 = globalThis.fetch;
807
+ if (mode !== "test-disabled") {
808
+ fetchPricing(BURN0_API_URL, originalFetch2).catch(() => {
809
+ });
810
+ }
811
+ var accumulatedEvents = [];
812
+ var ledger = mode === "dev-local" || mode === "test-enabled" ? new LocalLedger(process.cwd()) : null;
813
+ var batch = null;
814
+ if ((mode === "dev-cloud" || mode === "prod-cloud") && apiKey) {
815
+ batch = new BatchBuffer({
816
+ sizeThreshold: 50,
817
+ timeThresholdMs: 1e4,
818
+ maxSize: 500,
819
+ onFlush: (events) => {
820
+ shipEvents(events, apiKey, BURN0_API_URL, originalFetch2).catch(() => {
821
+ });
822
+ }
823
+ });
824
+ }
825
+ var dispatch = createDispatcher(mode, {
826
+ logEvent,
827
+ writeLedger: ledger ? (e) => ledger.write(e) : void 0,
828
+ addToBatch: batch ? (e) => batch.add(e) : void 0,
829
+ accumulate: (e) => accumulatedEvents.push(e)
830
+ });
831
+ var preloaded = checkImportOrder();
832
+ if (preloaded.length > 0) {
833
+ console.warn(`[burn0] Warning: These SDKs were imported before burn0 and may not be tracked: ${preloaded.join(", ")}. Move \`import 'burn0'\` to the top of your entry file.`);
834
+ }
835
+ if (canPatch() && mode !== "test-disabled") {
836
+ const onEvent = (event) => {
837
+ const enriched = enrichEvent(event);
838
+ dispatch(enriched);
839
+ };
840
+ patchFetch(onEvent);
841
+ patchHttp(onEvent);
842
+ markPatched();
843
+ }
844
+ if (mode === "prod-local") {
845
+ const startTime = Date.now();
846
+ process.on("beforeExit", () => {
847
+ if (accumulatedEvents.length > 0) {
848
+ const uptimeSeconds = (Date.now() - startTime) / 1e3;
849
+ console.log(formatProcessSummary(accumulatedEvents, uptimeSeconds));
850
+ }
851
+ });
852
+ }
853
+ if (batch) {
854
+ const exitFlush = () => {
855
+ batch.flush();
856
+ batch.destroy();
857
+ };
858
+ process.on("beforeExit", exitFlush);
859
+ process.on("SIGTERM", exitFlush);
860
+ process.on("SIGINT", exitFlush);
861
+ process.on("SIGHUP", exitFlush);
862
+ }
863
+ var restore = createRestorer({ unpatchFetch, unpatchHttp, resetGuard });
864
+ //# sourceMappingURL=register.js.map