@diogonzafe/tokenwatch 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/README.md +250 -0
- package/dist/cli.cjs +112 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +110 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +599 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +166 -0
- package/dist/index.d.ts +166 -0
- package/dist/index.js +567 -0
- package/dist/index.js.map +1 -0
- package/package.json +69 -0
- package/prices.json +18 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
createTracker: () => createTracker,
|
|
24
|
+
wrapAnthropic: () => wrapAnthropic,
|
|
25
|
+
wrapDeepSeek: () => wrapOpenAI,
|
|
26
|
+
wrapGemini: () => wrapGemini,
|
|
27
|
+
wrapOpenAI: () => wrapOpenAI
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(src_exports);
|
|
30
|
+
|
|
31
|
+
// src/core/tracker.ts
|
|
32
|
+
var import_zod = require("zod");
|
|
33
|
+
|
|
34
|
+
// src/core/pricing.ts
|
|
35
|
+
function resolvePrice(model, layers) {
|
|
36
|
+
const { customPrices, remotePrices, bundledPrices: bundledPrices2 } = layers;
|
|
37
|
+
const found = lookupInMap(model, customPrices) ?? lookupInMap(model, remotePrices) ?? lookupInMap(model, bundledPrices2);
|
|
38
|
+
if (found) return found;
|
|
39
|
+
console.warn(
|
|
40
|
+
`[tokenwatch] Unknown model "${model}". Cost will be recorded as $0. Add it via customPrices or update prices with: tokenwatch sync`
|
|
41
|
+
);
|
|
42
|
+
return { input: 0, output: 0 };
|
|
43
|
+
}
|
|
44
|
+
function lookupInMap(model, map) {
|
|
45
|
+
if (!map) return void 0;
|
|
46
|
+
if (model in map) return map[model];
|
|
47
|
+
for (const key of Object.keys(map)) {
|
|
48
|
+
if (model.startsWith(key) || key.startsWith(model)) {
|
|
49
|
+
return map[key];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return void 0;
|
|
53
|
+
}
|
|
54
|
+
function calculateCost(inputTokens, outputTokens, price) {
|
|
55
|
+
return inputTokens / 1e6 * price.input + outputTokens / 1e6 * price.output;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/core/storage.ts
|
|
59
|
+
var import_node_module = require("module");
|
|
60
|
+
var import_node_os = require("os");
|
|
61
|
+
var import_node_path = require("path");
|
|
62
|
+
var import_node_fs = require("fs");
|
|
63
|
+
var import_meta = {};
|
|
64
|
+
var MemoryStorage = class {
|
|
65
|
+
entries = [];
|
|
66
|
+
record(entry) {
|
|
67
|
+
this.entries.push(entry);
|
|
68
|
+
}
|
|
69
|
+
getAll() {
|
|
70
|
+
return [...this.entries];
|
|
71
|
+
}
|
|
72
|
+
clearAll() {
|
|
73
|
+
this.entries = [];
|
|
74
|
+
}
|
|
75
|
+
clearSession(sessionId) {
|
|
76
|
+
this.entries = this.entries.filter((e) => e.sessionId !== sessionId);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var DB_DIR = (0, import_node_path.join)((0, import_node_os.homedir)(), ".tokenwatch");
|
|
80
|
+
var DB_PATH = (0, import_node_path.join)(DB_DIR, "usage.db");
|
|
81
|
+
var SqliteStorage = class {
|
|
82
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
83
|
+
db;
|
|
84
|
+
constructor(dbPath = DB_PATH) {
|
|
85
|
+
let BetterSqlite3;
|
|
86
|
+
try {
|
|
87
|
+
const req = typeof globalThis.require === "function" ? (
|
|
88
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
89
|
+
globalThis.require
|
|
90
|
+
) : (0, import_node_module.createRequire)(import_meta.url);
|
|
91
|
+
BetterSqlite3 = req("better-sqlite3");
|
|
92
|
+
} catch {
|
|
93
|
+
throw new Error(
|
|
94
|
+
"[tokenwatch] SQLite storage requires better-sqlite3. Run: npm install better-sqlite3"
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
(0, import_node_fs.mkdirSync)(DB_DIR, { recursive: true });
|
|
98
|
+
this.db = new BetterSqlite3(dbPath);
|
|
99
|
+
this.migrate();
|
|
100
|
+
}
|
|
101
|
+
migrate() {
|
|
102
|
+
this.db.exec(`
|
|
103
|
+
CREATE TABLE IF NOT EXISTS usage (
|
|
104
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
105
|
+
model TEXT NOT NULL,
|
|
106
|
+
input_tokens INTEGER NOT NULL,
|
|
107
|
+
output_tokens INTEGER NOT NULL,
|
|
108
|
+
cost_usd REAL NOT NULL,
|
|
109
|
+
session_id TEXT,
|
|
110
|
+
user_id TEXT,
|
|
111
|
+
timestamp TEXT NOT NULL
|
|
112
|
+
)
|
|
113
|
+
`);
|
|
114
|
+
}
|
|
115
|
+
record(entry) {
|
|
116
|
+
this.db.prepare(
|
|
117
|
+
`INSERT INTO usage
|
|
118
|
+
(model, input_tokens, output_tokens, cost_usd, session_id, user_id, timestamp)
|
|
119
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
120
|
+
).run(
|
|
121
|
+
entry.model,
|
|
122
|
+
entry.inputTokens,
|
|
123
|
+
entry.outputTokens,
|
|
124
|
+
entry.costUSD,
|
|
125
|
+
entry.sessionId ?? null,
|
|
126
|
+
entry.userId ?? null,
|
|
127
|
+
entry.timestamp
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
getAll() {
|
|
131
|
+
const rows = this.db.prepare("SELECT * FROM usage").all();
|
|
132
|
+
return rows.map((r) => ({
|
|
133
|
+
model: r.model,
|
|
134
|
+
inputTokens: r.input_tokens,
|
|
135
|
+
outputTokens: r.output_tokens,
|
|
136
|
+
costUSD: r.cost_usd,
|
|
137
|
+
...r.session_id != null && { sessionId: r.session_id },
|
|
138
|
+
...r.user_id != null && { userId: r.user_id },
|
|
139
|
+
timestamp: r.timestamp
|
|
140
|
+
}));
|
|
141
|
+
}
|
|
142
|
+
clearAll() {
|
|
143
|
+
this.db.exec("DELETE FROM usage");
|
|
144
|
+
}
|
|
145
|
+
clearSession(sessionId) {
|
|
146
|
+
this.db.prepare("DELETE FROM usage WHERE session_id = ?").run(sessionId);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
function createStorage(type) {
|
|
150
|
+
if (type === "sqlite") return new SqliteStorage();
|
|
151
|
+
return new MemoryStorage();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/core/sync.ts
|
|
155
|
+
var import_promises = require("fs/promises");
|
|
156
|
+
var import_node_fs2 = require("fs");
|
|
157
|
+
var import_node_os2 = require("os");
|
|
158
|
+
var import_node_path2 = require("path");
|
|
159
|
+
var CACHE_DIR = (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".tokenwatch");
|
|
160
|
+
var CACHE_FILE = (0, import_node_path2.join)(CACHE_DIR, "prices.json");
|
|
161
|
+
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
162
|
+
var REMOTE_URL = "https://raw.githubusercontent.com/diogonzafe/tokenwatch/main/prices.json";
|
|
163
|
+
async function fetchRemotePrices(url = REMOTE_URL) {
|
|
164
|
+
try {
|
|
165
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(8e3) });
|
|
166
|
+
if (!res.ok) return null;
|
|
167
|
+
const data = await res.json();
|
|
168
|
+
if (!data?.models) return null;
|
|
169
|
+
await persistCache(data);
|
|
170
|
+
return data.models;
|
|
171
|
+
} catch {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async function loadCachedPrices() {
|
|
176
|
+
if (!(0, import_node_fs2.existsSync)(CACHE_FILE)) return null;
|
|
177
|
+
try {
|
|
178
|
+
const raw = await (0, import_promises.readFile)(CACHE_FILE, "utf8");
|
|
179
|
+
const data = JSON.parse(raw);
|
|
180
|
+
const age = Date.now() - (data._cachedAt ?? 0);
|
|
181
|
+
if (age > CACHE_TTL_MS) return null;
|
|
182
|
+
return data.models ?? null;
|
|
183
|
+
} catch {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async function persistCache(data) {
|
|
188
|
+
try {
|
|
189
|
+
await (0, import_promises.mkdir)(CACHE_DIR, { recursive: true });
|
|
190
|
+
const payload = { ...data, _cachedAt: Date.now() };
|
|
191
|
+
await (0, import_promises.writeFile)(CACHE_FILE, JSON.stringify(payload, null, 2), "utf8");
|
|
192
|
+
} catch {
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async function getRemotePrices() {
|
|
196
|
+
const cached = await loadCachedPrices();
|
|
197
|
+
if (cached) return cached;
|
|
198
|
+
return fetchRemotePrices();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// prices.json
|
|
202
|
+
var prices_default = {
|
|
203
|
+
updated_at: "2026-04-16",
|
|
204
|
+
source: "https://raw.githubusercontent.com/diogonzafe/tokenwatch/main/prices.json",
|
|
205
|
+
models: {
|
|
206
|
+
"gpt-4o": { input: 2.5, output: 10 },
|
|
207
|
+
"gpt-4o-mini": { input: 0.15, output: 0.6 },
|
|
208
|
+
"gpt-5": { input: 1.25, output: 10 },
|
|
209
|
+
"gpt-5-mini": { input: 0.25, output: 2 },
|
|
210
|
+
"gpt-5-nano": { input: 0.05, output: 0.4 },
|
|
211
|
+
"claude-opus-4-6": { input: 5, output: 25 },
|
|
212
|
+
"claude-sonnet-4-6": { input: 3, output: 15 },
|
|
213
|
+
"claude-haiku-4-5": { input: 1, output: 5 },
|
|
214
|
+
"gemini-2.5-pro": { input: 1.25, output: 10 },
|
|
215
|
+
"gemini-2.5-flash": { input: 0.3, output: 2.5 },
|
|
216
|
+
"deepseek-chat": { input: 0.28, output: 0.42 },
|
|
217
|
+
"deepseek-reasoner": { input: 0.55, output: 2.19 }
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// src/core/tracker.ts
|
|
222
|
+
var bundledPrices = prices_default.models;
|
|
223
|
+
var ModelPriceSchema = import_zod.z.object({
|
|
224
|
+
input: import_zod.z.number().nonnegative(),
|
|
225
|
+
output: import_zod.z.number().nonnegative()
|
|
226
|
+
});
|
|
227
|
+
var TrackerConfigSchema = import_zod.z.object({
|
|
228
|
+
storage: import_zod.z.enum(["memory", "sqlite"]).optional().default("memory"),
|
|
229
|
+
alertThreshold: import_zod.z.number().positive().optional(),
|
|
230
|
+
webhookUrl: import_zod.z.string().url().optional(),
|
|
231
|
+
syncPrices: import_zod.z.boolean().optional().default(true),
|
|
232
|
+
customPrices: import_zod.z.record(import_zod.z.string(), ModelPriceSchema).optional()
|
|
233
|
+
});
|
|
234
|
+
function createTracker(config = {}) {
|
|
235
|
+
const parsed = TrackerConfigSchema.safeParse(config);
|
|
236
|
+
if (!parsed.success) {
|
|
237
|
+
const issues = parsed.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n");
|
|
238
|
+
throw new Error(`[tokenwatch] Invalid config:
|
|
239
|
+
${issues}`);
|
|
240
|
+
}
|
|
241
|
+
const {
|
|
242
|
+
storage: storageType,
|
|
243
|
+
alertThreshold,
|
|
244
|
+
webhookUrl,
|
|
245
|
+
syncPrices,
|
|
246
|
+
customPrices
|
|
247
|
+
} = parsed.data;
|
|
248
|
+
const storage = createStorage(storageType);
|
|
249
|
+
let remotePrices;
|
|
250
|
+
if (syncPrices) {
|
|
251
|
+
getRemotePrices().then((result) => {
|
|
252
|
+
if (result) remotePrices = result;
|
|
253
|
+
}).catch(() => {
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
let alertFired = false;
|
|
257
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
258
|
+
function resolveModelPrice(model) {
|
|
259
|
+
return resolvePrice(model, {
|
|
260
|
+
bundledPrices,
|
|
261
|
+
...customPrices !== void 0 && { customPrices },
|
|
262
|
+
...remotePrices !== void 0 && { remotePrices }
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
function track(entry) {
|
|
266
|
+
const price = resolveModelPrice(entry.model);
|
|
267
|
+
const costUSD = calculateCost(entry.inputTokens, entry.outputTokens, price);
|
|
268
|
+
const full = {
|
|
269
|
+
...entry,
|
|
270
|
+
costUSD,
|
|
271
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
272
|
+
};
|
|
273
|
+
storage.record(full);
|
|
274
|
+
maybeFireAlert();
|
|
275
|
+
}
|
|
276
|
+
function maybeFireAlert() {
|
|
277
|
+
if (!alertThreshold || !webhookUrl || alertFired) return;
|
|
278
|
+
const total = computeTotal(storage.getAll());
|
|
279
|
+
if (total >= alertThreshold) {
|
|
280
|
+
alertFired = true;
|
|
281
|
+
const payload = {
|
|
282
|
+
text: `[tokenwatch] Alert: total cost reached $${total.toFixed(4)} USD (threshold: $${alertThreshold})`
|
|
283
|
+
};
|
|
284
|
+
fetch(webhookUrl, {
|
|
285
|
+
method: "POST",
|
|
286
|
+
headers: { "Content-Type": "application/json" },
|
|
287
|
+
body: JSON.stringify(payload)
|
|
288
|
+
}).catch(() => {
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
function getReport() {
|
|
293
|
+
const entries = storage.getAll();
|
|
294
|
+
const byModel = {};
|
|
295
|
+
const bySession = {};
|
|
296
|
+
const byUser = {};
|
|
297
|
+
let totalInput = 0;
|
|
298
|
+
let totalOutput = 0;
|
|
299
|
+
let totalCost = 0;
|
|
300
|
+
let lastTimestamp = startedAt;
|
|
301
|
+
for (const e of entries) {
|
|
302
|
+
totalInput += e.inputTokens;
|
|
303
|
+
totalOutput += e.outputTokens;
|
|
304
|
+
totalCost += e.costUSD;
|
|
305
|
+
if (e.timestamp > lastTimestamp) lastTimestamp = e.timestamp;
|
|
306
|
+
const m = byModel[e.model] ??= { costUSD: 0, calls: 0, tokens: { input: 0, output: 0 } };
|
|
307
|
+
m.costUSD += e.costUSD;
|
|
308
|
+
m.calls += 1;
|
|
309
|
+
m.tokens.input += e.inputTokens;
|
|
310
|
+
m.tokens.output += e.outputTokens;
|
|
311
|
+
if (e.sessionId) {
|
|
312
|
+
const s = bySession[e.sessionId] ??= { costUSD: 0, calls: 0 };
|
|
313
|
+
s.costUSD += e.costUSD;
|
|
314
|
+
s.calls += 1;
|
|
315
|
+
}
|
|
316
|
+
if (e.userId) {
|
|
317
|
+
const u = byUser[e.userId] ??= { costUSD: 0, calls: 0 };
|
|
318
|
+
u.costUSD += e.costUSD;
|
|
319
|
+
u.calls += 1;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return {
|
|
323
|
+
totalCostUSD: totalCost,
|
|
324
|
+
totalTokens: { input: totalInput, output: totalOutput },
|
|
325
|
+
byModel,
|
|
326
|
+
bySession,
|
|
327
|
+
byUser,
|
|
328
|
+
period: { from: startedAt, to: lastTimestamp }
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
function reset() {
|
|
332
|
+
storage.clearAll();
|
|
333
|
+
alertFired = false;
|
|
334
|
+
}
|
|
335
|
+
function resetSession(sessionId) {
|
|
336
|
+
storage.clearSession(sessionId);
|
|
337
|
+
}
|
|
338
|
+
function exportJSON() {
|
|
339
|
+
return JSON.stringify(getReport(), null, 2);
|
|
340
|
+
}
|
|
341
|
+
function exportCSV() {
|
|
342
|
+
const entries = storage.getAll();
|
|
343
|
+
const header = "timestamp,model,inputTokens,outputTokens,costUSD,sessionId,userId";
|
|
344
|
+
const rows = entries.map(
|
|
345
|
+
(e) => [
|
|
346
|
+
e.timestamp,
|
|
347
|
+
e.model,
|
|
348
|
+
e.inputTokens,
|
|
349
|
+
e.outputTokens,
|
|
350
|
+
e.costUSD.toFixed(8),
|
|
351
|
+
e.sessionId ?? "",
|
|
352
|
+
e.userId ?? ""
|
|
353
|
+
].join(",")
|
|
354
|
+
);
|
|
355
|
+
return [header, ...rows].join("\n");
|
|
356
|
+
}
|
|
357
|
+
return { track, getReport, reset, resetSession, exportJSON, exportCSV };
|
|
358
|
+
}
|
|
359
|
+
function computeTotal(entries) {
|
|
360
|
+
return entries.reduce((sum, e) => sum + e.costUSD, 0);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// src/providers/openai.ts
|
|
364
|
+
function extractMeta(params) {
|
|
365
|
+
const { __sessionId, __userId, ...cleaned } = params;
|
|
366
|
+
return {
|
|
367
|
+
cleaned,
|
|
368
|
+
sessionId: typeof __sessionId === "string" ? __sessionId : void 0,
|
|
369
|
+
userId: typeof __userId === "string" ? __userId : void 0
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
function extractUsage(usage) {
|
|
373
|
+
if (!usage) return { inputTokens: 0, outputTokens: 0 };
|
|
374
|
+
return {
|
|
375
|
+
inputTokens: usage.prompt_tokens ?? usage.input_tokens ?? 0,
|
|
376
|
+
outputTokens: usage.completion_tokens ?? usage.output_tokens ?? 0
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
function trackWithMeta(tracker, model, inputTokens, outputTokens, sessionId, userId) {
|
|
380
|
+
tracker.track({
|
|
381
|
+
model,
|
|
382
|
+
inputTokens,
|
|
383
|
+
outputTokens,
|
|
384
|
+
...sessionId !== void 0 && { sessionId },
|
|
385
|
+
...userId !== void 0 && { userId }
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
async function* wrapStream(stream, model, sessionId, userId, tracker) {
|
|
389
|
+
let lastChunk;
|
|
390
|
+
for await (const chunk of stream) {
|
|
391
|
+
lastChunk = chunk;
|
|
392
|
+
yield chunk;
|
|
393
|
+
}
|
|
394
|
+
if (lastChunk?.usage) {
|
|
395
|
+
const { inputTokens, outputTokens } = extractUsage(lastChunk.usage);
|
|
396
|
+
trackWithMeta(tracker, model, inputTokens, outputTokens, sessionId, userId);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
function wrapOpenAI(client, tracker) {
|
|
400
|
+
const proxiedCompletions = new Proxy(client.chat.completions, {
|
|
401
|
+
get(target, prop) {
|
|
402
|
+
if (prop !== "create")
|
|
403
|
+
return target[prop];
|
|
404
|
+
return async function(params) {
|
|
405
|
+
const { cleaned, sessionId, userId } = extractMeta(params);
|
|
406
|
+
const model = typeof cleaned["model"] === "string" ? cleaned["model"] : "unknown";
|
|
407
|
+
let result;
|
|
408
|
+
try {
|
|
409
|
+
result = await target.create(cleaned);
|
|
410
|
+
} catch (err) {
|
|
411
|
+
throw err;
|
|
412
|
+
}
|
|
413
|
+
if (result && typeof result === "object" && Symbol.asyncIterator in result) {
|
|
414
|
+
return wrapStream(
|
|
415
|
+
result,
|
|
416
|
+
model,
|
|
417
|
+
sessionId,
|
|
418
|
+
userId,
|
|
419
|
+
tracker
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
const completion = result;
|
|
423
|
+
const { inputTokens, outputTokens } = extractUsage(completion.usage);
|
|
424
|
+
trackWithMeta(
|
|
425
|
+
tracker,
|
|
426
|
+
completion.model ?? model,
|
|
427
|
+
inputTokens,
|
|
428
|
+
outputTokens,
|
|
429
|
+
sessionId,
|
|
430
|
+
userId
|
|
431
|
+
);
|
|
432
|
+
return result;
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
const proxiedChat = new Proxy(client.chat, {
|
|
437
|
+
get(target, prop) {
|
|
438
|
+
if (prop === "completions") return proxiedCompletions;
|
|
439
|
+
return target[prop];
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
return new Proxy(client, {
|
|
443
|
+
get(target, prop) {
|
|
444
|
+
if (prop === "chat") return proxiedChat;
|
|
445
|
+
return target[prop];
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// src/providers/anthropic.ts
|
|
451
|
+
function extractMeta2(params) {
|
|
452
|
+
const { __sessionId, __userId, ...cleaned } = params;
|
|
453
|
+
return {
|
|
454
|
+
cleaned,
|
|
455
|
+
sessionId: typeof __sessionId === "string" ? __sessionId : void 0,
|
|
456
|
+
userId: typeof __userId === "string" ? __userId : void 0
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
function extractUsage2(usage) {
|
|
460
|
+
if (!usage) return { inputTokens: 0, outputTokens: 0 };
|
|
461
|
+
return {
|
|
462
|
+
inputTokens: usage.input_tokens ?? 0,
|
|
463
|
+
outputTokens: usage.output_tokens ?? 0
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
function trackWithMeta2(tracker, model, inputTokens, outputTokens, sessionId, userId) {
|
|
467
|
+
tracker.track({
|
|
468
|
+
model,
|
|
469
|
+
inputTokens,
|
|
470
|
+
outputTokens,
|
|
471
|
+
...sessionId !== void 0 && { sessionId },
|
|
472
|
+
...userId !== void 0 && { userId }
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
async function* wrapStream2(stream, model, sessionId, userId, tracker) {
|
|
476
|
+
let inputTokens = 0;
|
|
477
|
+
let outputTokens = 0;
|
|
478
|
+
for await (const event of stream) {
|
|
479
|
+
yield event;
|
|
480
|
+
if (event.type === "message_start" && event.message?.usage) {
|
|
481
|
+
inputTokens = event.message.usage.input_tokens ?? 0;
|
|
482
|
+
}
|
|
483
|
+
if (event.type === "message_delta" && event.usage) {
|
|
484
|
+
outputTokens = event.usage.output_tokens ?? 0;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if (inputTokens > 0 || outputTokens > 0) {
|
|
488
|
+
trackWithMeta2(tracker, model, inputTokens, outputTokens, sessionId, userId);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
function wrapAnthropic(client, tracker) {
|
|
492
|
+
const proxiedMessages = new Proxy(client.messages, {
|
|
493
|
+
get(target, prop) {
|
|
494
|
+
if (prop !== "create")
|
|
495
|
+
return target[prop];
|
|
496
|
+
return async function(params) {
|
|
497
|
+
const { cleaned, sessionId, userId } = extractMeta2(params);
|
|
498
|
+
const model = typeof cleaned["model"] === "string" ? cleaned["model"] : "unknown";
|
|
499
|
+
let result;
|
|
500
|
+
try {
|
|
501
|
+
result = await target.create(cleaned);
|
|
502
|
+
} catch (err) {
|
|
503
|
+
throw err;
|
|
504
|
+
}
|
|
505
|
+
if (result && typeof result === "object" && Symbol.asyncIterator in result) {
|
|
506
|
+
return wrapStream2(
|
|
507
|
+
result,
|
|
508
|
+
model,
|
|
509
|
+
sessionId,
|
|
510
|
+
userId,
|
|
511
|
+
tracker
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
const message = result;
|
|
515
|
+
const { inputTokens, outputTokens } = extractUsage2(message.usage);
|
|
516
|
+
trackWithMeta2(
|
|
517
|
+
tracker,
|
|
518
|
+
message.model ?? model,
|
|
519
|
+
inputTokens,
|
|
520
|
+
outputTokens,
|
|
521
|
+
sessionId,
|
|
522
|
+
userId
|
|
523
|
+
);
|
|
524
|
+
return result;
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
return new Proxy(client, {
|
|
529
|
+
get(target, prop) {
|
|
530
|
+
if (prop === "messages") return proxiedMessages;
|
|
531
|
+
return target[prop];
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// src/providers/gemini.ts
|
|
537
|
+
function wrapGemini(client, tracker) {
|
|
538
|
+
return new Proxy(client, {
|
|
539
|
+
get(target, prop) {
|
|
540
|
+
if (prop !== "getGenerativeModel")
|
|
541
|
+
return target[prop];
|
|
542
|
+
return function(modelParams) {
|
|
543
|
+
const modelInstance = target.getGenerativeModel(modelParams);
|
|
544
|
+
const modelId = modelParams.model;
|
|
545
|
+
return new Proxy(modelInstance, {
|
|
546
|
+
get(mTarget, mProp) {
|
|
547
|
+
if (mProp === "generateContent") {
|
|
548
|
+
return async function(params) {
|
|
549
|
+
let result;
|
|
550
|
+
try {
|
|
551
|
+
result = await mTarget.generateContent(params);
|
|
552
|
+
} catch (err) {
|
|
553
|
+
throw err;
|
|
554
|
+
}
|
|
555
|
+
const meta = result.response.usageMetadata;
|
|
556
|
+
tracker.track({
|
|
557
|
+
model: modelId,
|
|
558
|
+
inputTokens: meta?.promptTokenCount ?? 0,
|
|
559
|
+
outputTokens: meta?.candidatesTokenCount ?? 0
|
|
560
|
+
});
|
|
561
|
+
return result;
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
if (mProp === "generateContentStream") {
|
|
565
|
+
return async function(params) {
|
|
566
|
+
let streamResult;
|
|
567
|
+
try {
|
|
568
|
+
streamResult = await mTarget.generateContentStream(params);
|
|
569
|
+
} catch (err) {
|
|
570
|
+
throw err;
|
|
571
|
+
}
|
|
572
|
+
streamResult.response.then((res) => {
|
|
573
|
+
const meta = res.usageMetadata;
|
|
574
|
+
tracker.track({
|
|
575
|
+
model: modelId,
|
|
576
|
+
inputTokens: meta?.promptTokenCount ?? 0,
|
|
577
|
+
outputTokens: meta?.candidatesTokenCount ?? 0
|
|
578
|
+
});
|
|
579
|
+
}).catch(() => {
|
|
580
|
+
});
|
|
581
|
+
return streamResult;
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
return mTarget[mProp];
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
592
|
+
0 && (module.exports = {
|
|
593
|
+
createTracker,
|
|
594
|
+
wrapAnthropic,
|
|
595
|
+
wrapDeepSeek,
|
|
596
|
+
wrapGemini,
|
|
597
|
+
wrapOpenAI
|
|
598
|
+
});
|
|
599
|
+
//# sourceMappingURL=index.cjs.map
|