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