@hasna/economy 0.2.14 → 0.2.16
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/cli/index.js +312 -24
- package/dist/db/database.d.ts +13 -0
- package/dist/db/database.d.ts.map +1 -1
- package/dist/index.js +97 -23
- package/dist/ingest/billing.d.ts +18 -0
- package/dist/ingest/billing.d.ts.map +1 -0
- package/dist/lib/pricing.d.ts.map +1 -1
- package/dist/mcp/index.js +74 -23
- package/dist/server/index.js +74 -23
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -79,6 +79,7 @@ var DEFAULT_PRICING;
|
|
|
79
79
|
var init_pricing = __esm(() => {
|
|
80
80
|
init_database();
|
|
81
81
|
DEFAULT_PRICING = {
|
|
82
|
+
"claude-opus-4-7": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25 },
|
|
82
83
|
"claude-opus-4-6": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25 },
|
|
83
84
|
"claude-opus-4-5": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25 },
|
|
84
85
|
"claude-sonnet-4-6": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75 },
|
|
@@ -89,24 +90,82 @@ var init_pricing = __esm(() => {
|
|
|
89
90
|
"claude-3-opus": { inputPer1M: 15, outputPer1M: 75, cacheReadPer1M: 1.5, cacheWritePer1M: 18.75 },
|
|
90
91
|
"claude-3-sonnet": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75 },
|
|
91
92
|
"claude-3-haiku": { inputPer1M: 0.25, outputPer1M: 1.25, cacheReadPer1M: 0.03, cacheWritePer1M: 0.3 },
|
|
92
|
-
"gemini-
|
|
93
|
+
"gemini-3.1-pro": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0.31, cacheWritePer1M: 0 },
|
|
93
94
|
"gemini-2.5-pro": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
95
|
+
"gemini-2.5-flash": { inputPer1M: 0.15, outputPer1M: 0.6, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
96
|
+
"gemini-2.0-flash": { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
94
97
|
"gemini-1.5-pro": { inputPer1M: 1.25, outputPer1M: 5, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
95
98
|
"gemini-1.5-flash": { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
99
|
+
"gpt-5.4": { inputPer1M: 2.5, outputPer1M: 15, cacheReadPer1M: 0.25, cacheWritePer1M: 0 },
|
|
100
|
+
"gpt-5.4-pro": { inputPer1M: 30, outputPer1M: 180, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
101
|
+
"gpt-5.4-mini": { inputPer1M: 0.75, outputPer1M: 4.5, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
|
|
96
102
|
"gpt-5.3-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
103
|
+
"gpt-5.3-chat": { inputPer1M: 2, outputPer1M: 8, cacheReadPer1M: 0.5, cacheWritePer1M: 0 },
|
|
97
104
|
"gpt-5.2-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
98
105
|
"gpt-5-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
106
|
+
"gpt-5-mini": { inputPer1M: 0.3, outputPer1M: 1.2, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
|
|
107
|
+
"gpt-5.2": { inputPer1M: 2, outputPer1M: 8, cacheReadPer1M: 0.5, cacheWritePer1M: 0 },
|
|
99
108
|
"gpt-4o": { inputPer1M: 2.5, outputPer1M: 10, cacheReadPer1M: 1.25, cacheWritePer1M: 0 },
|
|
100
109
|
"gpt-4o-mini": { inputPer1M: 0.15, outputPer1M: 0.6, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
|
|
101
110
|
o1: { inputPer1M: 15, outputPer1M: 60, cacheReadPer1M: 7.5, cacheWritePer1M: 0 },
|
|
102
111
|
"o1-mini": { inputPer1M: 3, outputPer1M: 12, cacheReadPer1M: 1.5, cacheWritePer1M: 0 },
|
|
103
112
|
o3: { inputPer1M: 10, outputPer1M: 40, cacheReadPer1M: 2.5, cacheWritePer1M: 0 },
|
|
104
113
|
"o3-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.55, cacheWritePer1M: 0 },
|
|
105
|
-
"o4-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.275, cacheWritePer1M: 0 }
|
|
114
|
+
"o4-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.275, cacheWritePer1M: 0 },
|
|
115
|
+
"qwen3.6-plus": { inputPer1M: 0.8, outputPer1M: 2, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
116
|
+
"qwen3.6": { inputPer1M: 0.3, outputPer1M: 0.6, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
117
|
+
"minimax-m2.7": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
118
|
+
"minimax-m2.7-highspeed": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
119
|
+
"minimax-m1": { inputPer1M: 0.2, outputPer1M: 1.1, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
120
|
+
"grok-3": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
121
|
+
"grok-3-mini": { inputPer1M: 0.3, outputPer1M: 0.5, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
122
|
+
"glm-5.1": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
123
|
+
"glm-5": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
124
|
+
"kimi-k2": { inputPer1M: 0.6, outputPer1M: 0.6, cacheReadPer1M: 0, cacheWritePer1M: 0 }
|
|
106
125
|
};
|
|
107
126
|
});
|
|
108
127
|
|
|
109
128
|
// src/db/database.ts
|
|
129
|
+
var exports_database = {};
|
|
130
|
+
__export(exports_database, {
|
|
131
|
+
upsertSession: () => upsertSession,
|
|
132
|
+
upsertRequest: () => upsertRequest,
|
|
133
|
+
upsertProject: () => upsertProject,
|
|
134
|
+
upsertModelPricing: () => upsertModelPricing,
|
|
135
|
+
upsertGoal: () => upsertGoal,
|
|
136
|
+
upsertBudget: () => upsertBudget,
|
|
137
|
+
upsertBillingDaily: () => upsertBillingDaily,
|
|
138
|
+
setIngestState: () => setIngestState,
|
|
139
|
+
seedModelPricing: () => seedModelPricing,
|
|
140
|
+
rollupSession: () => rollupSession,
|
|
141
|
+
queryTopSessions: () => queryTopSessions,
|
|
142
|
+
querySummary: () => querySummary,
|
|
143
|
+
querySessions: () => querySessions,
|
|
144
|
+
queryRequestsSince: () => queryRequestsSince,
|
|
145
|
+
queryProjectBreakdown: () => queryProjectBreakdown,
|
|
146
|
+
queryModelBreakdown: () => queryModelBreakdown,
|
|
147
|
+
queryDailyBreakdown: () => queryDailyBreakdown,
|
|
148
|
+
queryBillingSummary: () => queryBillingSummary,
|
|
149
|
+
openDatabase: () => openDatabase,
|
|
150
|
+
listProjects: () => listProjects,
|
|
151
|
+
listModelPricing: () => listModelPricing,
|
|
152
|
+
listMachines: () => listMachines,
|
|
153
|
+
listGoals: () => listGoals,
|
|
154
|
+
listBudgets: () => listBudgets,
|
|
155
|
+
getProject: () => getProject,
|
|
156
|
+
getModelPricing: () => getModelPricing,
|
|
157
|
+
getMachineId: () => getMachineId,
|
|
158
|
+
getIngestState: () => getIngestState,
|
|
159
|
+
getGoalStatuses: () => getGoalStatuses,
|
|
160
|
+
getDbPath: () => getDbPath,
|
|
161
|
+
getDataDir: () => getDataDir,
|
|
162
|
+
getBudgetStatuses: () => getBudgetStatuses,
|
|
163
|
+
deleteProject: () => deleteProject,
|
|
164
|
+
deleteModelPricing: () => deleteModelPricing,
|
|
165
|
+
deleteGoal: () => deleteGoal,
|
|
166
|
+
deleteBudget: () => deleteBudget,
|
|
167
|
+
clearBillingRange: () => clearBillingRange
|
|
168
|
+
});
|
|
110
169
|
import { SqliteAdapter as Database } from "@hasna/cloud";
|
|
111
170
|
import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
112
171
|
import { hostname } from "os";
|
|
@@ -253,6 +312,18 @@ function initSchema(db) {
|
|
|
253
312
|
machine_id TEXT,
|
|
254
313
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
255
314
|
);
|
|
315
|
+
|
|
316
|
+
CREATE TABLE IF NOT EXISTS billing_daily (
|
|
317
|
+
date TEXT NOT NULL,
|
|
318
|
+
provider TEXT NOT NULL,
|
|
319
|
+
description TEXT DEFAULT '',
|
|
320
|
+
cost_usd REAL NOT NULL DEFAULT 0,
|
|
321
|
+
updated_at TEXT NOT NULL,
|
|
322
|
+
PRIMARY KEY (date, provider, description)
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
CREATE INDEX IF NOT EXISTS idx_billing_date ON billing_daily(date);
|
|
326
|
+
CREATE INDEX IF NOT EXISTS idx_billing_provider ON billing_daily(provider);
|
|
256
327
|
`);
|
|
257
328
|
const cols = db.prepare(`PRAGMA table_info(requests)`).all();
|
|
258
329
|
if (!cols.some((c) => c.name === "machine_id")) {
|
|
@@ -271,11 +342,11 @@ function periodWhere(period) {
|
|
|
271
342
|
case "yesterday":
|
|
272
343
|
return `DATE(timestamp) = DATE('now', '-1 day')`;
|
|
273
344
|
case "week":
|
|
274
|
-
return `timestamp >= DATE('now', '-7 days')`;
|
|
345
|
+
return `timestamp >= DATE('now', 'weekday 0', '-7 days')`;
|
|
275
346
|
case "month":
|
|
276
|
-
return `timestamp >= DATE('now', '
|
|
347
|
+
return `timestamp >= DATE('now', 'start of month')`;
|
|
277
348
|
case "year":
|
|
278
|
-
return `timestamp >= DATE('now', '
|
|
349
|
+
return `timestamp >= DATE('now', 'start of year')`;
|
|
279
350
|
case "all":
|
|
280
351
|
return "1=1";
|
|
281
352
|
}
|
|
@@ -287,11 +358,11 @@ function sessionPeriodWhere(period) {
|
|
|
287
358
|
case "yesterday":
|
|
288
359
|
return `DATE(started_at) = DATE('now', '-1 day')`;
|
|
289
360
|
case "week":
|
|
290
|
-
return `started_at >= DATE('now', '-7 days')`;
|
|
361
|
+
return `started_at >= DATE('now', 'weekday 0', '-7 days')`;
|
|
291
362
|
case "month":
|
|
292
|
-
return `started_at >= DATE('now', '
|
|
363
|
+
return `started_at >= DATE('now', 'start of month')`;
|
|
293
364
|
case "year":
|
|
294
|
-
return `started_at >= DATE('now', '
|
|
365
|
+
return `started_at >= DATE('now', 'start of year')`;
|
|
295
366
|
case "all":
|
|
296
367
|
return "1=1";
|
|
297
368
|
}
|
|
@@ -403,19 +474,39 @@ function queryModelBreakdown(db) {
|
|
|
403
474
|
}
|
|
404
475
|
function queryProjectBreakdown(db) {
|
|
405
476
|
return db.prepare(`
|
|
477
|
+
WITH labeled AS (
|
|
478
|
+
SELECT
|
|
479
|
+
s.id,
|
|
480
|
+
s.project_path,
|
|
481
|
+
s.total_cost_usd,
|
|
482
|
+
s.started_at,
|
|
483
|
+
COALESCE(
|
|
484
|
+
NULLIF(s.project_name, ''),
|
|
485
|
+
CASE
|
|
486
|
+
WHEN s.project_path LIKE '%/%'
|
|
487
|
+
THEN substr(s.project_path, length(rtrim(s.project_path, replace(s.project_path, '/', ''))) + 1)
|
|
488
|
+
ELSE s.project_path
|
|
489
|
+
END
|
|
490
|
+
) as label
|
|
491
|
+
FROM sessions s
|
|
492
|
+
WHERE s.project_path != '' OR s.project_name != ''
|
|
493
|
+
)
|
|
406
494
|
SELECT
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
COUNT(DISTINCT
|
|
410
|
-
COUNT(r.id) as requests,
|
|
411
|
-
COALESCE(
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
495
|
+
MIN(l.project_path) as project_path,
|
|
496
|
+
l.label as project_name,
|
|
497
|
+
COUNT(DISTINCT l.id) as sessions,
|
|
498
|
+
COALESCE((SELECT COUNT(*) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)), 0) as requests,
|
|
499
|
+
COALESCE(
|
|
500
|
+
(SELECT SUM(r.cost_usd) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)),
|
|
501
|
+
SUM(l.total_cost_usd)
|
|
502
|
+
) as cost_usd,
|
|
503
|
+
COALESCE(
|
|
504
|
+
(SELECT SUM(r.input_tokens + r.output_tokens + r.cache_read_tokens + r.cache_create_tokens) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)),
|
|
505
|
+
0
|
|
506
|
+
) as total_tokens,
|
|
507
|
+
MAX(l.started_at) as last_active
|
|
508
|
+
FROM labeled l
|
|
509
|
+
GROUP BY l.label
|
|
419
510
|
ORDER BY cost_usd DESC
|
|
420
511
|
`).all();
|
|
421
512
|
}
|
|
@@ -535,6 +626,26 @@ function setIngestState(db, source, key, value) {
|
|
|
535
626
|
function queryRequestsSince(db, since) {
|
|
536
627
|
return db.prepare(`SELECT * FROM requests WHERE timestamp > ? ORDER BY timestamp ASC`).all(since);
|
|
537
628
|
}
|
|
629
|
+
function upsertBillingDaily(db, row) {
|
|
630
|
+
db.prepare(`
|
|
631
|
+
INSERT OR REPLACE INTO billing_daily (date, provider, description, cost_usd, updated_at)
|
|
632
|
+
VALUES (?, ?, ?, ?, ?)
|
|
633
|
+
`).run(row.date, row.provider, row.description, row.cost_usd, row.updated_at);
|
|
634
|
+
}
|
|
635
|
+
function clearBillingRange(db, provider, fromDate, toDate) {
|
|
636
|
+
db.prepare(`DELETE FROM billing_daily WHERE provider = ? AND date >= ? AND date <= ?`).run(provider, fromDate, toDate);
|
|
637
|
+
}
|
|
638
|
+
function queryBillingSummary(db, period) {
|
|
639
|
+
const where = period === "today" ? `date = DATE('now')` : period === "yesterday" ? `date = DATE('now', '-1 day')` : period === "week" ? `date >= DATE('now', 'weekday 0', '-7 days')` : period === "month" ? `date >= DATE('now', 'start of month')` : period === "year" ? `date >= DATE('now', 'start of year')` : "1=1";
|
|
640
|
+
const rows = db.prepare(`SELECT provider, SUM(cost_usd) as cost FROM billing_daily WHERE ${where} GROUP BY provider`).all();
|
|
641
|
+
const by_provider = {};
|
|
642
|
+
let total = 0;
|
|
643
|
+
for (const r of rows) {
|
|
644
|
+
by_provider[r.provider] = r.cost;
|
|
645
|
+
total += r.cost;
|
|
646
|
+
}
|
|
647
|
+
return { total_usd: total, by_provider };
|
|
648
|
+
}
|
|
538
649
|
function listMachines(db) {
|
|
539
650
|
return db.prepare(`
|
|
540
651
|
SELECT
|
|
@@ -566,11 +677,11 @@ function deleteModelPricing(db, model) {
|
|
|
566
677
|
db.prepare(`DELETE FROM model_pricing WHERE model = ?`).run(model);
|
|
567
678
|
}
|
|
568
679
|
function seedModelPricing(db, defaults) {
|
|
569
|
-
const existing = db.prepare(`SELECT
|
|
570
|
-
if (existing.count > 0)
|
|
571
|
-
return;
|
|
680
|
+
const existing = new Set(db.prepare(`SELECT model FROM model_pricing`).all().map((r) => r.model));
|
|
572
681
|
const now = new Date().toISOString();
|
|
573
682
|
for (const [model, p] of Object.entries(defaults)) {
|
|
683
|
+
if (existing.has(model))
|
|
684
|
+
continue;
|
|
574
685
|
upsertModelPricing(db, {
|
|
575
686
|
model,
|
|
576
687
|
input_per_1m: p.inputPer1M,
|
|
@@ -1915,6 +2026,119 @@ init_claude();
|
|
|
1915
2026
|
init_codex();
|
|
1916
2027
|
init_gemini();
|
|
1917
2028
|
|
|
2029
|
+
// src/ingest/billing.ts
|
|
2030
|
+
init_database();
|
|
2031
|
+
function getAnthropicAdminKey() {
|
|
2032
|
+
return process.env["HASNAXYZ_ANTHROPIC_LIVE_ADMIN_API_KEY"] ?? process.env["ANTHROPIC_ADMIN_API_KEY"] ?? null;
|
|
2033
|
+
}
|
|
2034
|
+
function getOpenAIAdminKey() {
|
|
2035
|
+
return process.env["HASNAXYZ_OPENAI_LIVE_ADMIN_API_KEY"] ?? process.env["OPENAI_ADMIN_API_KEY"] ?? null;
|
|
2036
|
+
}
|
|
2037
|
+
function toISODate(d) {
|
|
2038
|
+
return d.toISOString().substring(0, 10);
|
|
2039
|
+
}
|
|
2040
|
+
async function syncAnthropicBilling(db, opts = {}) {
|
|
2041
|
+
const key = getAnthropicAdminKey();
|
|
2042
|
+
if (!key)
|
|
2043
|
+
throw new Error("Missing Anthropic admin key (HASNAXYZ_ANTHROPIC_LIVE_ADMIN_API_KEY)");
|
|
2044
|
+
const now = new Date;
|
|
2045
|
+
const end = opts.toDate ? new Date(opts.toDate) : new Date(now.getTime() + 24 * 3600000);
|
|
2046
|
+
const days = opts.days ?? 31;
|
|
2047
|
+
const start = opts.fromDate ? new Date(opts.fromDate) : new Date(end.getTime() - days * 24 * 3600000);
|
|
2048
|
+
const startIso = start.toISOString().replace(/\.\d+/, "").replace(/:\d{2}Z$/, ":00Z");
|
|
2049
|
+
const endIso = end.toISOString().replace(/\.\d+/, "").replace(/:\d{2}Z$/, ":00Z");
|
|
2050
|
+
let totalUsd = 0;
|
|
2051
|
+
const buckets = [];
|
|
2052
|
+
let nextPage;
|
|
2053
|
+
do {
|
|
2054
|
+
const url = new URL("https://api.anthropic.com/v1/organizations/cost_report");
|
|
2055
|
+
url.searchParams.set("starting_at", startIso);
|
|
2056
|
+
url.searchParams.set("ending_at", endIso);
|
|
2057
|
+
url.searchParams.set("bucket_width", "1d");
|
|
2058
|
+
url.searchParams.set("limit", "31");
|
|
2059
|
+
url.searchParams.append("group_by[]", "description");
|
|
2060
|
+
if (nextPage)
|
|
2061
|
+
url.searchParams.set("page", nextPage);
|
|
2062
|
+
const res = await fetch(url.toString(), {
|
|
2063
|
+
headers: { "anthropic-version": "2023-06-01", "x-api-key": key }
|
|
2064
|
+
});
|
|
2065
|
+
const data = await res.json();
|
|
2066
|
+
if (data.error)
|
|
2067
|
+
throw new Error(`Anthropic API: ${data.error.message}`);
|
|
2068
|
+
if (data.data)
|
|
2069
|
+
buckets.push(...data.data);
|
|
2070
|
+
nextPage = data.has_more ? data.next_page : undefined;
|
|
2071
|
+
} while (nextPage);
|
|
2072
|
+
const fromDateStr = toISODate(start);
|
|
2073
|
+
const toDateStr = toISODate(new Date(end.getTime() - 1000));
|
|
2074
|
+
clearBillingRange(db, "anthropic", fromDateStr, toDateStr);
|
|
2075
|
+
const updatedAt = new Date().toISOString();
|
|
2076
|
+
for (const bucket of buckets) {
|
|
2077
|
+
const date = bucket.starting_at.substring(0, 10);
|
|
2078
|
+
for (const r of bucket.results) {
|
|
2079
|
+
const usd = Number(r.amount) / 100;
|
|
2080
|
+
if (usd === 0)
|
|
2081
|
+
continue;
|
|
2082
|
+
const desc = (r.description ?? "unknown").substring(0, 200);
|
|
2083
|
+
upsertBillingDaily(db, { date, provider: "anthropic", description: desc, cost_usd: usd, updated_at: updatedAt });
|
|
2084
|
+
totalUsd += usd;
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
return { days: buckets.length, totalUsd };
|
|
2088
|
+
}
|
|
2089
|
+
async function syncOpenAIBilling(db, opts = {}) {
|
|
2090
|
+
const key = getOpenAIAdminKey();
|
|
2091
|
+
if (!key)
|
|
2092
|
+
throw new Error("Missing OpenAI admin key (HASNAXYZ_OPENAI_LIVE_ADMIN_API_KEY)");
|
|
2093
|
+
const now = new Date;
|
|
2094
|
+
const end = opts.toDate ? new Date(opts.toDate) : now;
|
|
2095
|
+
const days = opts.days ?? 31;
|
|
2096
|
+
const start = opts.fromDate ? new Date(opts.fromDate) : new Date(end.getTime() - days * 24 * 3600000);
|
|
2097
|
+
const startSec = Math.floor(start.getTime() / 1000);
|
|
2098
|
+
const endSec = Math.floor(end.getTime() / 1000);
|
|
2099
|
+
let totalUsd = 0;
|
|
2100
|
+
const buckets = [];
|
|
2101
|
+
let nextPage;
|
|
2102
|
+
do {
|
|
2103
|
+
const url = new URL("https://api.openai.com/v1/organization/costs");
|
|
2104
|
+
url.searchParams.set("start_time", String(startSec));
|
|
2105
|
+
url.searchParams.set("end_time", String(endSec));
|
|
2106
|
+
url.searchParams.set("bucket_width", "1d");
|
|
2107
|
+
url.searchParams.set("limit", "31");
|
|
2108
|
+
url.searchParams.append("group_by[]", "line_item");
|
|
2109
|
+
if (nextPage)
|
|
2110
|
+
url.searchParams.set("page", nextPage);
|
|
2111
|
+
const res = await fetch(url.toString(), {
|
|
2112
|
+
headers: { Authorization: `Bearer ${key}` }
|
|
2113
|
+
});
|
|
2114
|
+
const data = await res.json();
|
|
2115
|
+
if (data.error)
|
|
2116
|
+
throw new Error(`OpenAI API: ${data.error.message}`);
|
|
2117
|
+
if (data.data)
|
|
2118
|
+
buckets.push(...data.data);
|
|
2119
|
+
nextPage = data.has_more ? data.next_page : undefined;
|
|
2120
|
+
} while (nextPage);
|
|
2121
|
+
const fromDateStr = toISODate(start);
|
|
2122
|
+
const toDateStr = toISODate(new Date(end.getTime() - 1000));
|
|
2123
|
+
clearBillingRange(db, "openai", fromDateStr, toDateStr);
|
|
2124
|
+
const updatedAt = new Date().toISOString();
|
|
2125
|
+
for (const bucket of buckets) {
|
|
2126
|
+
const date = new Date(bucket.start_time * 1000).toISOString().substring(0, 10);
|
|
2127
|
+
for (const r of bucket.results) {
|
|
2128
|
+
const usd = Number(r.amount?.value ?? 0);
|
|
2129
|
+
if (usd === 0)
|
|
2130
|
+
continue;
|
|
2131
|
+
const desc = (r.line_item ?? "unknown").substring(0, 200);
|
|
2132
|
+
upsertBillingDaily(db, { date, provider: "openai", description: desc, cost_usd: usd, updated_at: updatedAt });
|
|
2133
|
+
totalUsd += usd;
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
return { days: buckets.length, totalUsd };
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
// src/cli/index.ts
|
|
2140
|
+
init_database();
|
|
2141
|
+
|
|
1918
2142
|
// src/lib/package-metadata.ts
|
|
1919
2143
|
import { readFileSync as readFileSync5 } from "fs";
|
|
1920
2144
|
var cachedMetadata = null;
|
|
@@ -2054,7 +2278,7 @@ program.action(async () => {
|
|
|
2054
2278
|
}
|
|
2055
2279
|
console.log();
|
|
2056
2280
|
});
|
|
2057
|
-
program.command("sync").description("Ingest cost data from Claude Code, Codex, and Gemini").option("--claude", "Only ingest Claude Code telemetry").option("--takumi", "Only ingest Takumi sessions").option("--codex", "Only ingest Codex sessions").option("--gemini", "Only ingest Gemini CLI sessions").option("-v, --verbose", "Verbose output").option("--force", "Force re-process all files (ignore mtime cache)").option("--backfill-machine", "Tag existing records that have no machine_id with current hostname").action(async (opts) => {
|
|
2281
|
+
program.command("sync").description("Ingest cost data from Claude Code, Codex, and Gemini").option("--claude", "Only ingest Claude Code telemetry").option("--takumi", "Only ingest Takumi sessions").option("--codex", "Only ingest Codex sessions").option("--gemini", "Only ingest Gemini CLI sessions").option("-v, --verbose", "Verbose output").option("--force", "Force re-process all files (ignore mtime cache)").option("--backfill-machine", "Tag existing records that have no machine_id with current hostname").option("--recalculate", "Recalculate costs for all requests with cost_usd = 0").action(async (opts) => {
|
|
2058
2282
|
const db = openDatabase();
|
|
2059
2283
|
ensurePricingSeeded(db);
|
|
2060
2284
|
if (opts.force) {
|
|
@@ -2093,6 +2317,29 @@ program.command("sync").description("Ingest cost data from Claude Code, Codex, a
|
|
|
2093
2317
|
const sessCount = db.prepare(`UPDATE sessions SET machine_id = ? WHERE machine_id = '' OR machine_id IS NULL`).run(machine);
|
|
2094
2318
|
console.log(chalk4.cyan(`\u2192 Backfilled machine_id='${machine}': ${reqCount.changes} requests, ${sessCount.changes} sessions`));
|
|
2095
2319
|
}
|
|
2320
|
+
if (opts.recalculate) {
|
|
2321
|
+
const { computeCostFromDb: computeCostFromDb2 } = await Promise.resolve().then(() => (init_pricing(), exports_pricing));
|
|
2322
|
+
const zeroRows = db.prepare(`SELECT id, model, input_tokens, output_tokens, cache_read_tokens, cache_create_tokens FROM requests WHERE cost_usd = 0 AND (input_tokens > 0 OR output_tokens > 0)`).all();
|
|
2323
|
+
let fixed = 0;
|
|
2324
|
+
for (const r of zeroRows) {
|
|
2325
|
+
const cost = computeCostFromDb2(db, r.model, r.input_tokens, r.output_tokens, r.cache_read_tokens, r.cache_create_tokens);
|
|
2326
|
+
if (cost > 0) {
|
|
2327
|
+
db.prepare(`UPDATE requests SET cost_usd = ? WHERE id = ?`).run(cost, r.id);
|
|
2328
|
+
fixed++;
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
if (fixed > 0) {
|
|
2332
|
+
const touchedSessions = new Set(zeroRows.map((r) => {
|
|
2333
|
+
const row = db.prepare(`SELECT session_id FROM requests WHERE id = ?`).get(r.id);
|
|
2334
|
+
return row?.session_id;
|
|
2335
|
+
}).filter(Boolean));
|
|
2336
|
+
const { rollupSession: rollupSession2 } = await Promise.resolve().then(() => (init_database(), exports_database));
|
|
2337
|
+
for (const sid of touchedSessions) {
|
|
2338
|
+
rollupSession2(db, sid);
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
console.log(chalk4.cyan(`\u2192 Recalculated: ${fixed}/${zeroRows.length} zero-cost requests now have pricing`));
|
|
2342
|
+
}
|
|
2096
2343
|
try {
|
|
2097
2344
|
const { checkAndFireWebhooks: checkAndFireWebhooks2 } = await Promise.resolve().then(() => (init_webhooks(), exports_webhooks));
|
|
2098
2345
|
await checkAndFireWebhooks2(db);
|
|
@@ -2956,5 +3203,46 @@ cloudCmd.command("status").description("Check cloud connection status").action(a
|
|
|
2956
3203
|
}
|
|
2957
3204
|
console.log();
|
|
2958
3205
|
});
|
|
3206
|
+
var billingCmd = program.command("billing").description("Pull actual billing from provider admin APIs (ground truth)");
|
|
3207
|
+
billingCmd.command("sync").description("Sync actual billing from Anthropic and OpenAI admin APIs").option("--days <n>", "Days of history to fetch", "31").option("--anthropic", "Only sync Anthropic").option("--openai", "Only sync OpenAI").action(async (opts) => {
|
|
3208
|
+
const db = openDatabase();
|
|
3209
|
+
const days = Number(opts.days ?? 31);
|
|
3210
|
+
const doBoth = !opts.anthropic && !opts.openai;
|
|
3211
|
+
if (opts.anthropic || doBoth) {
|
|
3212
|
+
process.stdout.write(chalk4.cyan("\u2192 Syncing Anthropic billing... "));
|
|
3213
|
+
try {
|
|
3214
|
+
const r = await syncAnthropicBilling(db, { days });
|
|
3215
|
+
console.log(chalk4.green(`\u2713 ${r.days} days, $${r.totalUsd.toFixed(2)}`));
|
|
3216
|
+
} catch (e) {
|
|
3217
|
+
console.log(chalk4.red(`\u2717 ${e instanceof Error ? e.message : String(e)}`));
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
if (opts.openai || doBoth) {
|
|
3221
|
+
process.stdout.write(chalk4.cyan("\u2192 Syncing OpenAI billing... "));
|
|
3222
|
+
try {
|
|
3223
|
+
const r = await syncOpenAIBilling(db, { days });
|
|
3224
|
+
console.log(chalk4.green(`\u2713 ${r.days} days, $${r.totalUsd.toFixed(2)}`));
|
|
3225
|
+
} catch (e) {
|
|
3226
|
+
console.log(chalk4.red(`\u2717 ${e instanceof Error ? e.message : String(e)}`));
|
|
3227
|
+
}
|
|
3228
|
+
}
|
|
3229
|
+
});
|
|
3230
|
+
billingCmd.command("show").description("Show actual billing totals vs our estimated costs").option("--period <p>", "Period: today|yesterday|week|month|year|all", "month").action((opts) => {
|
|
3231
|
+
const db = openDatabase();
|
|
3232
|
+
const period = opts.period ?? "month";
|
|
3233
|
+
const actual = queryBillingSummary(db, period);
|
|
3234
|
+
const estimated = querySummary(db, period);
|
|
3235
|
+
console.log();
|
|
3236
|
+
console.log(chalk4.bold.cyan(` Billing ${period} (actual from admin APIs)
|
|
3237
|
+
`));
|
|
3238
|
+
printTable(["Provider", "Actual (billed)"], Object.entries(actual.by_provider).map(([p, c]) => [chalk4.white(p), fmt2(c)]));
|
|
3239
|
+
console.log();
|
|
3240
|
+
console.log(` ${chalk4.bold("Actual total:")} ${fmt2(actual.total_usd)}`);
|
|
3241
|
+
console.log(` ${chalk4.dim("Our estimate:")} ${fmt2(estimated.total_usd)}`);
|
|
3242
|
+
const diff = estimated.total_usd - actual.total_usd;
|
|
3243
|
+
const pct = actual.total_usd > 0 ? diff / actual.total_usd * 100 : 0;
|
|
3244
|
+
console.log(` ${chalk4.dim("Difference:")} ${fmt2(Math.abs(diff))} (${diff >= 0 ? "+" : ""}${pct.toFixed(1)}%)`);
|
|
3245
|
+
console.log();
|
|
3246
|
+
});
|
|
2959
3247
|
registerBrainsCommand(program);
|
|
2960
3248
|
program.parse();
|
package/dist/db/database.d.ts
CHANGED
|
@@ -48,6 +48,19 @@ export declare function getGoalStatuses(db: Database): GoalStatus[];
|
|
|
48
48
|
export declare function getIngestState(db: Database, source: string, key: string): string | null;
|
|
49
49
|
export declare function setIngestState(db: Database, source: string, key: string, value: string): void;
|
|
50
50
|
export declare function queryRequestsSince(db: Database, since: string): EconomyRequest[];
|
|
51
|
+
export interface BillingDaily {
|
|
52
|
+
date: string;
|
|
53
|
+
provider: 'anthropic' | 'openai' | string;
|
|
54
|
+
description: string;
|
|
55
|
+
cost_usd: number;
|
|
56
|
+
updated_at: string;
|
|
57
|
+
}
|
|
58
|
+
export declare function upsertBillingDaily(db: Database, row: BillingDaily): void;
|
|
59
|
+
export declare function clearBillingRange(db: Database, provider: string, fromDate: string, toDate: string): void;
|
|
60
|
+
export declare function queryBillingSummary(db: Database, period: Period): {
|
|
61
|
+
total_usd: number;
|
|
62
|
+
by_provider: Record<string, number>;
|
|
63
|
+
};
|
|
51
64
|
export interface MachineInfo {
|
|
52
65
|
machine_id: string;
|
|
53
66
|
sessions: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAKxD,OAAO,KAAK,EACV,cAAc,EACd,cAAc,EACd,cAAc,EACd,MAAM,EACN,YAAY,EACZ,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,MAAM,EACN,aAAa,EACd,MAAM,mBAAmB,CAAA;AAE1B,wBAAgB,YAAY,IAAI,MAAM,CAKrC;AAED,wBAAgB,UAAU,IAAI,MAAM,CAkBnC;AAED,wBAAgB,SAAS,IAAI,MAAM,CAIlC;AAED,wBAAgB,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,UAAQ,GAAG,QAAQ,CAgBxE;
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/db/database.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -77,6 +77,7 @@ var DEFAULT_PRICING;
|
|
|
77
77
|
var init_pricing = __esm(() => {
|
|
78
78
|
init_database();
|
|
79
79
|
DEFAULT_PRICING = {
|
|
80
|
+
"claude-opus-4-7": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25 },
|
|
80
81
|
"claude-opus-4-6": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25 },
|
|
81
82
|
"claude-opus-4-5": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25 },
|
|
82
83
|
"claude-sonnet-4-6": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75 },
|
|
@@ -87,20 +88,38 @@ var init_pricing = __esm(() => {
|
|
|
87
88
|
"claude-3-opus": { inputPer1M: 15, outputPer1M: 75, cacheReadPer1M: 1.5, cacheWritePer1M: 18.75 },
|
|
88
89
|
"claude-3-sonnet": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75 },
|
|
89
90
|
"claude-3-haiku": { inputPer1M: 0.25, outputPer1M: 1.25, cacheReadPer1M: 0.03, cacheWritePer1M: 0.3 },
|
|
90
|
-
"gemini-
|
|
91
|
+
"gemini-3.1-pro": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0.31, cacheWritePer1M: 0 },
|
|
91
92
|
"gemini-2.5-pro": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
93
|
+
"gemini-2.5-flash": { inputPer1M: 0.15, outputPer1M: 0.6, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
94
|
+
"gemini-2.0-flash": { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
92
95
|
"gemini-1.5-pro": { inputPer1M: 1.25, outputPer1M: 5, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
93
96
|
"gemini-1.5-flash": { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
97
|
+
"gpt-5.4": { inputPer1M: 2.5, outputPer1M: 15, cacheReadPer1M: 0.25, cacheWritePer1M: 0 },
|
|
98
|
+
"gpt-5.4-pro": { inputPer1M: 30, outputPer1M: 180, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
99
|
+
"gpt-5.4-mini": { inputPer1M: 0.75, outputPer1M: 4.5, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
|
|
94
100
|
"gpt-5.3-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
101
|
+
"gpt-5.3-chat": { inputPer1M: 2, outputPer1M: 8, cacheReadPer1M: 0.5, cacheWritePer1M: 0 },
|
|
95
102
|
"gpt-5.2-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
96
103
|
"gpt-5-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
104
|
+
"gpt-5-mini": { inputPer1M: 0.3, outputPer1M: 1.2, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
|
|
105
|
+
"gpt-5.2": { inputPer1M: 2, outputPer1M: 8, cacheReadPer1M: 0.5, cacheWritePer1M: 0 },
|
|
97
106
|
"gpt-4o": { inputPer1M: 2.5, outputPer1M: 10, cacheReadPer1M: 1.25, cacheWritePer1M: 0 },
|
|
98
107
|
"gpt-4o-mini": { inputPer1M: 0.15, outputPer1M: 0.6, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
|
|
99
108
|
o1: { inputPer1M: 15, outputPer1M: 60, cacheReadPer1M: 7.5, cacheWritePer1M: 0 },
|
|
100
109
|
"o1-mini": { inputPer1M: 3, outputPer1M: 12, cacheReadPer1M: 1.5, cacheWritePer1M: 0 },
|
|
101
110
|
o3: { inputPer1M: 10, outputPer1M: 40, cacheReadPer1M: 2.5, cacheWritePer1M: 0 },
|
|
102
111
|
"o3-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.55, cacheWritePer1M: 0 },
|
|
103
|
-
"o4-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.275, cacheWritePer1M: 0 }
|
|
112
|
+
"o4-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.275, cacheWritePer1M: 0 },
|
|
113
|
+
"qwen3.6-plus": { inputPer1M: 0.8, outputPer1M: 2, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
114
|
+
"qwen3.6": { inputPer1M: 0.3, outputPer1M: 0.6, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
115
|
+
"minimax-m2.7": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
116
|
+
"minimax-m2.7-highspeed": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
117
|
+
"minimax-m1": { inputPer1M: 0.2, outputPer1M: 1.1, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
118
|
+
"grok-3": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
119
|
+
"grok-3-mini": { inputPer1M: 0.3, outputPer1M: 0.5, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
120
|
+
"glm-5.1": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
121
|
+
"glm-5": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
122
|
+
"kimi-k2": { inputPer1M: 0.6, outputPer1M: 0.6, cacheReadPer1M: 0, cacheWritePer1M: 0 }
|
|
104
123
|
};
|
|
105
124
|
});
|
|
106
125
|
|
|
@@ -251,6 +270,18 @@ function initSchema(db) {
|
|
|
251
270
|
machine_id TEXT,
|
|
252
271
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
253
272
|
);
|
|
273
|
+
|
|
274
|
+
CREATE TABLE IF NOT EXISTS billing_daily (
|
|
275
|
+
date TEXT NOT NULL,
|
|
276
|
+
provider TEXT NOT NULL,
|
|
277
|
+
description TEXT DEFAULT '',
|
|
278
|
+
cost_usd REAL NOT NULL DEFAULT 0,
|
|
279
|
+
updated_at TEXT NOT NULL,
|
|
280
|
+
PRIMARY KEY (date, provider, description)
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
CREATE INDEX IF NOT EXISTS idx_billing_date ON billing_daily(date);
|
|
284
|
+
CREATE INDEX IF NOT EXISTS idx_billing_provider ON billing_daily(provider);
|
|
254
285
|
`);
|
|
255
286
|
const cols = db.prepare(`PRAGMA table_info(requests)`).all();
|
|
256
287
|
if (!cols.some((c) => c.name === "machine_id")) {
|
|
@@ -269,11 +300,11 @@ function periodWhere(period) {
|
|
|
269
300
|
case "yesterday":
|
|
270
301
|
return `DATE(timestamp) = DATE('now', '-1 day')`;
|
|
271
302
|
case "week":
|
|
272
|
-
return `timestamp >= DATE('now', '-7 days')`;
|
|
303
|
+
return `timestamp >= DATE('now', 'weekday 0', '-7 days')`;
|
|
273
304
|
case "month":
|
|
274
|
-
return `timestamp >= DATE('now', '
|
|
305
|
+
return `timestamp >= DATE('now', 'start of month')`;
|
|
275
306
|
case "year":
|
|
276
|
-
return `timestamp >= DATE('now', '
|
|
307
|
+
return `timestamp >= DATE('now', 'start of year')`;
|
|
277
308
|
case "all":
|
|
278
309
|
return "1=1";
|
|
279
310
|
}
|
|
@@ -285,11 +316,11 @@ function sessionPeriodWhere(period) {
|
|
|
285
316
|
case "yesterday":
|
|
286
317
|
return `DATE(started_at) = DATE('now', '-1 day')`;
|
|
287
318
|
case "week":
|
|
288
|
-
return `started_at >= DATE('now', '-7 days')`;
|
|
319
|
+
return `started_at >= DATE('now', 'weekday 0', '-7 days')`;
|
|
289
320
|
case "month":
|
|
290
|
-
return `started_at >= DATE('now', '
|
|
321
|
+
return `started_at >= DATE('now', 'start of month')`;
|
|
291
322
|
case "year":
|
|
292
|
-
return `started_at >= DATE('now', '
|
|
323
|
+
return `started_at >= DATE('now', 'start of year')`;
|
|
293
324
|
case "all":
|
|
294
325
|
return "1=1";
|
|
295
326
|
}
|
|
@@ -401,19 +432,39 @@ function queryModelBreakdown(db) {
|
|
|
401
432
|
}
|
|
402
433
|
function queryProjectBreakdown(db) {
|
|
403
434
|
return db.prepare(`
|
|
435
|
+
WITH labeled AS (
|
|
436
|
+
SELECT
|
|
437
|
+
s.id,
|
|
438
|
+
s.project_path,
|
|
439
|
+
s.total_cost_usd,
|
|
440
|
+
s.started_at,
|
|
441
|
+
COALESCE(
|
|
442
|
+
NULLIF(s.project_name, ''),
|
|
443
|
+
CASE
|
|
444
|
+
WHEN s.project_path LIKE '%/%'
|
|
445
|
+
THEN substr(s.project_path, length(rtrim(s.project_path, replace(s.project_path, '/', ''))) + 1)
|
|
446
|
+
ELSE s.project_path
|
|
447
|
+
END
|
|
448
|
+
) as label
|
|
449
|
+
FROM sessions s
|
|
450
|
+
WHERE s.project_path != '' OR s.project_name != ''
|
|
451
|
+
)
|
|
404
452
|
SELECT
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
COUNT(DISTINCT
|
|
408
|
-
COUNT(r.id) as requests,
|
|
409
|
-
COALESCE(
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
453
|
+
MIN(l.project_path) as project_path,
|
|
454
|
+
l.label as project_name,
|
|
455
|
+
COUNT(DISTINCT l.id) as sessions,
|
|
456
|
+
COALESCE((SELECT COUNT(*) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)), 0) as requests,
|
|
457
|
+
COALESCE(
|
|
458
|
+
(SELECT SUM(r.cost_usd) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)),
|
|
459
|
+
SUM(l.total_cost_usd)
|
|
460
|
+
) as cost_usd,
|
|
461
|
+
COALESCE(
|
|
462
|
+
(SELECT SUM(r.input_tokens + r.output_tokens + r.cache_read_tokens + r.cache_create_tokens) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)),
|
|
463
|
+
0
|
|
464
|
+
) as total_tokens,
|
|
465
|
+
MAX(l.started_at) as last_active
|
|
466
|
+
FROM labeled l
|
|
467
|
+
GROUP BY l.label
|
|
417
468
|
ORDER BY cost_usd DESC
|
|
418
469
|
`).all();
|
|
419
470
|
}
|
|
@@ -533,6 +584,26 @@ function setIngestState(db, source, key, value) {
|
|
|
533
584
|
function queryRequestsSince(db, since) {
|
|
534
585
|
return db.prepare(`SELECT * FROM requests WHERE timestamp > ? ORDER BY timestamp ASC`).all(since);
|
|
535
586
|
}
|
|
587
|
+
function upsertBillingDaily(db, row) {
|
|
588
|
+
db.prepare(`
|
|
589
|
+
INSERT OR REPLACE INTO billing_daily (date, provider, description, cost_usd, updated_at)
|
|
590
|
+
VALUES (?, ?, ?, ?, ?)
|
|
591
|
+
`).run(row.date, row.provider, row.description, row.cost_usd, row.updated_at);
|
|
592
|
+
}
|
|
593
|
+
function clearBillingRange(db, provider, fromDate, toDate) {
|
|
594
|
+
db.prepare(`DELETE FROM billing_daily WHERE provider = ? AND date >= ? AND date <= ?`).run(provider, fromDate, toDate);
|
|
595
|
+
}
|
|
596
|
+
function queryBillingSummary(db, period) {
|
|
597
|
+
const where = period === "today" ? `date = DATE('now')` : period === "yesterday" ? `date = DATE('now', '-1 day')` : period === "week" ? `date >= DATE('now', 'weekday 0', '-7 days')` : period === "month" ? `date >= DATE('now', 'start of month')` : period === "year" ? `date >= DATE('now', 'start of year')` : "1=1";
|
|
598
|
+
const rows = db.prepare(`SELECT provider, SUM(cost_usd) as cost FROM billing_daily WHERE ${where} GROUP BY provider`).all();
|
|
599
|
+
const by_provider = {};
|
|
600
|
+
let total = 0;
|
|
601
|
+
for (const r of rows) {
|
|
602
|
+
by_provider[r.provider] = r.cost;
|
|
603
|
+
total += r.cost;
|
|
604
|
+
}
|
|
605
|
+
return { total_usd: total, by_provider };
|
|
606
|
+
}
|
|
536
607
|
function listMachines(db) {
|
|
537
608
|
return db.prepare(`
|
|
538
609
|
SELECT
|
|
@@ -564,11 +635,11 @@ function deleteModelPricing(db, model) {
|
|
|
564
635
|
db.prepare(`DELETE FROM model_pricing WHERE model = ?`).run(model);
|
|
565
636
|
}
|
|
566
637
|
function seedModelPricing(db, defaults) {
|
|
567
|
-
const existing = db.prepare(`SELECT
|
|
568
|
-
if (existing.count > 0)
|
|
569
|
-
return;
|
|
638
|
+
const existing = new Set(db.prepare(`SELECT model FROM model_pricing`).all().map((r) => r.model));
|
|
570
639
|
const now = new Date().toISOString();
|
|
571
640
|
for (const [model, p] of Object.entries(defaults)) {
|
|
641
|
+
if (existing.has(model))
|
|
642
|
+
continue;
|
|
572
643
|
upsertModelPricing(db, {
|
|
573
644
|
model,
|
|
574
645
|
input_per_1m: p.inputPer1M,
|
|
@@ -1018,6 +1089,7 @@ export {
|
|
|
1018
1089
|
upsertModelPricing,
|
|
1019
1090
|
upsertGoal,
|
|
1020
1091
|
upsertBudget,
|
|
1092
|
+
upsertBillingDaily,
|
|
1021
1093
|
setIngestState,
|
|
1022
1094
|
setActiveModel,
|
|
1023
1095
|
seedModelPricing,
|
|
@@ -1030,6 +1102,7 @@ export {
|
|
|
1030
1102
|
queryProjectBreakdown,
|
|
1031
1103
|
queryModelBreakdown,
|
|
1032
1104
|
queryDailyBreakdown,
|
|
1105
|
+
queryBillingSummary,
|
|
1033
1106
|
openDatabase,
|
|
1034
1107
|
normalizeModelName,
|
|
1035
1108
|
listProjects,
|
|
@@ -1059,6 +1132,7 @@ export {
|
|
|
1059
1132
|
deleteBudget,
|
|
1060
1133
|
computeCostFromDb,
|
|
1061
1134
|
computeCost,
|
|
1135
|
+
clearBillingRange,
|
|
1062
1136
|
clearActiveModel,
|
|
1063
1137
|
DEFAULT_PRICING,
|
|
1064
1138
|
DEFAULT_MODEL
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SqliteAdapter as Database } from '@hasna/cloud';
|
|
2
|
+
export declare function syncAnthropicBilling(db: Database, opts?: {
|
|
3
|
+
days?: number;
|
|
4
|
+
fromDate?: string;
|
|
5
|
+
toDate?: string;
|
|
6
|
+
}): Promise<{
|
|
7
|
+
days: number;
|
|
8
|
+
totalUsd: number;
|
|
9
|
+
}>;
|
|
10
|
+
export declare function syncOpenAIBilling(db: Database, opts?: {
|
|
11
|
+
days?: number;
|
|
12
|
+
fromDate?: string;
|
|
13
|
+
toDate?: string;
|
|
14
|
+
}): Promise<{
|
|
15
|
+
days: number;
|
|
16
|
+
totalUsd: number;
|
|
17
|
+
}>;
|
|
18
|
+
//# sourceMappingURL=billing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"billing.d.ts","sourceRoot":"","sources":["../../src/ingest/billing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAgC7D,wBAAsB,oBAAoB,CACxC,EAAE,EAAE,QAAQ,EACZ,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/D,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAuD7C;AAeD,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,QAAQ,EACZ,IAAI,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/D,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAqD7C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pricing.d.ts","sourceRoot":"","sources":["../../src/lib/pricing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAKrD,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"pricing.d.ts","sourceRoot":"","sources":["../../src/lib/pricing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAKrD,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAqDxD,CAAA;AAGD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKtD;AAGD,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAEtD;AAGD,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAuBjF;AAGD,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAO7D;AAED,wBAAgB,WAAW,CACzB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,eAAe,SAAI,EACnB,gBAAgB,SAAI,GACnB,MAAM,CASR;AAED,wBAAgB,iBAAiB,CAC/B,EAAE,EAAE,QAAQ,EACZ,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,eAAe,SAAI,EACnB,gBAAgB,SAAI,GACnB,MAAM,CASR"}
|
package/dist/mcp/index.js
CHANGED
|
@@ -78,6 +78,7 @@ var DEFAULT_PRICING;
|
|
|
78
78
|
var init_pricing = __esm(() => {
|
|
79
79
|
init_database();
|
|
80
80
|
DEFAULT_PRICING = {
|
|
81
|
+
"claude-opus-4-7": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25 },
|
|
81
82
|
"claude-opus-4-6": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25 },
|
|
82
83
|
"claude-opus-4-5": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25 },
|
|
83
84
|
"claude-sonnet-4-6": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75 },
|
|
@@ -88,20 +89,38 @@ var init_pricing = __esm(() => {
|
|
|
88
89
|
"claude-3-opus": { inputPer1M: 15, outputPer1M: 75, cacheReadPer1M: 1.5, cacheWritePer1M: 18.75 },
|
|
89
90
|
"claude-3-sonnet": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75 },
|
|
90
91
|
"claude-3-haiku": { inputPer1M: 0.25, outputPer1M: 1.25, cacheReadPer1M: 0.03, cacheWritePer1M: 0.3 },
|
|
91
|
-
"gemini-
|
|
92
|
+
"gemini-3.1-pro": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0.31, cacheWritePer1M: 0 },
|
|
92
93
|
"gemini-2.5-pro": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
94
|
+
"gemini-2.5-flash": { inputPer1M: 0.15, outputPer1M: 0.6, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
95
|
+
"gemini-2.0-flash": { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
93
96
|
"gemini-1.5-pro": { inputPer1M: 1.25, outputPer1M: 5, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
94
97
|
"gemini-1.5-flash": { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
98
|
+
"gpt-5.4": { inputPer1M: 2.5, outputPer1M: 15, cacheReadPer1M: 0.25, cacheWritePer1M: 0 },
|
|
99
|
+
"gpt-5.4-pro": { inputPer1M: 30, outputPer1M: 180, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
100
|
+
"gpt-5.4-mini": { inputPer1M: 0.75, outputPer1M: 4.5, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
|
|
95
101
|
"gpt-5.3-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
102
|
+
"gpt-5.3-chat": { inputPer1M: 2, outputPer1M: 8, cacheReadPer1M: 0.5, cacheWritePer1M: 0 },
|
|
96
103
|
"gpt-5.2-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
97
104
|
"gpt-5-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
105
|
+
"gpt-5-mini": { inputPer1M: 0.3, outputPer1M: 1.2, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
|
|
106
|
+
"gpt-5.2": { inputPer1M: 2, outputPer1M: 8, cacheReadPer1M: 0.5, cacheWritePer1M: 0 },
|
|
98
107
|
"gpt-4o": { inputPer1M: 2.5, outputPer1M: 10, cacheReadPer1M: 1.25, cacheWritePer1M: 0 },
|
|
99
108
|
"gpt-4o-mini": { inputPer1M: 0.15, outputPer1M: 0.6, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
|
|
100
109
|
o1: { inputPer1M: 15, outputPer1M: 60, cacheReadPer1M: 7.5, cacheWritePer1M: 0 },
|
|
101
110
|
"o1-mini": { inputPer1M: 3, outputPer1M: 12, cacheReadPer1M: 1.5, cacheWritePer1M: 0 },
|
|
102
111
|
o3: { inputPer1M: 10, outputPer1M: 40, cacheReadPer1M: 2.5, cacheWritePer1M: 0 },
|
|
103
112
|
"o3-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.55, cacheWritePer1M: 0 },
|
|
104
|
-
"o4-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.275, cacheWritePer1M: 0 }
|
|
113
|
+
"o4-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.275, cacheWritePer1M: 0 },
|
|
114
|
+
"qwen3.6-plus": { inputPer1M: 0.8, outputPer1M: 2, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
115
|
+
"qwen3.6": { inputPer1M: 0.3, outputPer1M: 0.6, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
116
|
+
"minimax-m2.7": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
117
|
+
"minimax-m2.7-highspeed": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
118
|
+
"minimax-m1": { inputPer1M: 0.2, outputPer1M: 1.1, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
119
|
+
"grok-3": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
120
|
+
"grok-3-mini": { inputPer1M: 0.3, outputPer1M: 0.5, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
121
|
+
"glm-5.1": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
122
|
+
"glm-5": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
123
|
+
"kimi-k2": { inputPer1M: 0.6, outputPer1M: 0.6, cacheReadPer1M: 0, cacheWritePer1M: 0 }
|
|
105
124
|
};
|
|
106
125
|
});
|
|
107
126
|
|
|
@@ -252,6 +271,18 @@ function initSchema(db) {
|
|
|
252
271
|
machine_id TEXT,
|
|
253
272
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
254
273
|
);
|
|
274
|
+
|
|
275
|
+
CREATE TABLE IF NOT EXISTS billing_daily (
|
|
276
|
+
date TEXT NOT NULL,
|
|
277
|
+
provider TEXT NOT NULL,
|
|
278
|
+
description TEXT DEFAULT '',
|
|
279
|
+
cost_usd REAL NOT NULL DEFAULT 0,
|
|
280
|
+
updated_at TEXT NOT NULL,
|
|
281
|
+
PRIMARY KEY (date, provider, description)
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
CREATE INDEX IF NOT EXISTS idx_billing_date ON billing_daily(date);
|
|
285
|
+
CREATE INDEX IF NOT EXISTS idx_billing_provider ON billing_daily(provider);
|
|
255
286
|
`);
|
|
256
287
|
const cols = db.prepare(`PRAGMA table_info(requests)`).all();
|
|
257
288
|
if (!cols.some((c) => c.name === "machine_id")) {
|
|
@@ -270,11 +301,11 @@ function periodWhere(period) {
|
|
|
270
301
|
case "yesterday":
|
|
271
302
|
return `DATE(timestamp) = DATE('now', '-1 day')`;
|
|
272
303
|
case "week":
|
|
273
|
-
return `timestamp >= DATE('now', '-7 days')`;
|
|
304
|
+
return `timestamp >= DATE('now', 'weekday 0', '-7 days')`;
|
|
274
305
|
case "month":
|
|
275
|
-
return `timestamp >= DATE('now', '
|
|
306
|
+
return `timestamp >= DATE('now', 'start of month')`;
|
|
276
307
|
case "year":
|
|
277
|
-
return `timestamp >= DATE('now', '
|
|
308
|
+
return `timestamp >= DATE('now', 'start of year')`;
|
|
278
309
|
case "all":
|
|
279
310
|
return "1=1";
|
|
280
311
|
}
|
|
@@ -286,11 +317,11 @@ function sessionPeriodWhere(period) {
|
|
|
286
317
|
case "yesterday":
|
|
287
318
|
return `DATE(started_at) = DATE('now', '-1 day')`;
|
|
288
319
|
case "week":
|
|
289
|
-
return `started_at >= DATE('now', '-7 days')`;
|
|
320
|
+
return `started_at >= DATE('now', 'weekday 0', '-7 days')`;
|
|
290
321
|
case "month":
|
|
291
|
-
return `started_at >= DATE('now', '
|
|
322
|
+
return `started_at >= DATE('now', 'start of month')`;
|
|
292
323
|
case "year":
|
|
293
|
-
return `started_at >= DATE('now', '
|
|
324
|
+
return `started_at >= DATE('now', 'start of year')`;
|
|
294
325
|
case "all":
|
|
295
326
|
return "1=1";
|
|
296
327
|
}
|
|
@@ -402,19 +433,39 @@ function queryModelBreakdown(db) {
|
|
|
402
433
|
}
|
|
403
434
|
function queryProjectBreakdown(db) {
|
|
404
435
|
return db.prepare(`
|
|
436
|
+
WITH labeled AS (
|
|
437
|
+
SELECT
|
|
438
|
+
s.id,
|
|
439
|
+
s.project_path,
|
|
440
|
+
s.total_cost_usd,
|
|
441
|
+
s.started_at,
|
|
442
|
+
COALESCE(
|
|
443
|
+
NULLIF(s.project_name, ''),
|
|
444
|
+
CASE
|
|
445
|
+
WHEN s.project_path LIKE '%/%'
|
|
446
|
+
THEN substr(s.project_path, length(rtrim(s.project_path, replace(s.project_path, '/', ''))) + 1)
|
|
447
|
+
ELSE s.project_path
|
|
448
|
+
END
|
|
449
|
+
) as label
|
|
450
|
+
FROM sessions s
|
|
451
|
+
WHERE s.project_path != '' OR s.project_name != ''
|
|
452
|
+
)
|
|
405
453
|
SELECT
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
COUNT(DISTINCT
|
|
409
|
-
COUNT(r.id) as requests,
|
|
410
|
-
COALESCE(
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
454
|
+
MIN(l.project_path) as project_path,
|
|
455
|
+
l.label as project_name,
|
|
456
|
+
COUNT(DISTINCT l.id) as sessions,
|
|
457
|
+
COALESCE((SELECT COUNT(*) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)), 0) as requests,
|
|
458
|
+
COALESCE(
|
|
459
|
+
(SELECT SUM(r.cost_usd) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)),
|
|
460
|
+
SUM(l.total_cost_usd)
|
|
461
|
+
) as cost_usd,
|
|
462
|
+
COALESCE(
|
|
463
|
+
(SELECT SUM(r.input_tokens + r.output_tokens + r.cache_read_tokens + r.cache_create_tokens) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)),
|
|
464
|
+
0
|
|
465
|
+
) as total_tokens,
|
|
466
|
+
MAX(l.started_at) as last_active
|
|
467
|
+
FROM labeled l
|
|
468
|
+
GROUP BY l.label
|
|
418
469
|
ORDER BY cost_usd DESC
|
|
419
470
|
`).all();
|
|
420
471
|
}
|
|
@@ -528,11 +579,11 @@ function getModelPricing(db, model) {
|
|
|
528
579
|
return db.prepare(`SELECT * FROM model_pricing WHERE model = ?`).get(model);
|
|
529
580
|
}
|
|
530
581
|
function seedModelPricing(db, defaults) {
|
|
531
|
-
const existing = db.prepare(`SELECT
|
|
532
|
-
if (existing.count > 0)
|
|
533
|
-
return;
|
|
582
|
+
const existing = new Set(db.prepare(`SELECT model FROM model_pricing`).all().map((r) => r.model));
|
|
534
583
|
const now = new Date().toISOString();
|
|
535
584
|
for (const [model, p] of Object.entries(defaults)) {
|
|
585
|
+
if (existing.has(model))
|
|
586
|
+
continue;
|
|
536
587
|
upsertModelPricing(db, {
|
|
537
588
|
model,
|
|
538
589
|
input_per_1m: p.inputPer1M,
|
package/dist/server/index.js
CHANGED
|
@@ -79,6 +79,7 @@ var DEFAULT_PRICING;
|
|
|
79
79
|
var init_pricing = __esm(() => {
|
|
80
80
|
init_database();
|
|
81
81
|
DEFAULT_PRICING = {
|
|
82
|
+
"claude-opus-4-7": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25 },
|
|
82
83
|
"claude-opus-4-6": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25 },
|
|
83
84
|
"claude-opus-4-5": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25 },
|
|
84
85
|
"claude-sonnet-4-6": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75 },
|
|
@@ -89,20 +90,38 @@ var init_pricing = __esm(() => {
|
|
|
89
90
|
"claude-3-opus": { inputPer1M: 15, outputPer1M: 75, cacheReadPer1M: 1.5, cacheWritePer1M: 18.75 },
|
|
90
91
|
"claude-3-sonnet": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75 },
|
|
91
92
|
"claude-3-haiku": { inputPer1M: 0.25, outputPer1M: 1.25, cacheReadPer1M: 0.03, cacheWritePer1M: 0.3 },
|
|
92
|
-
"gemini-
|
|
93
|
+
"gemini-3.1-pro": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0.31, cacheWritePer1M: 0 },
|
|
93
94
|
"gemini-2.5-pro": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
95
|
+
"gemini-2.5-flash": { inputPer1M: 0.15, outputPer1M: 0.6, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
96
|
+
"gemini-2.0-flash": { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
94
97
|
"gemini-1.5-pro": { inputPer1M: 1.25, outputPer1M: 5, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
95
98
|
"gemini-1.5-flash": { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
99
|
+
"gpt-5.4": { inputPer1M: 2.5, outputPer1M: 15, cacheReadPer1M: 0.25, cacheWritePer1M: 0 },
|
|
100
|
+
"gpt-5.4-pro": { inputPer1M: 30, outputPer1M: 180, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
101
|
+
"gpt-5.4-mini": { inputPer1M: 0.75, outputPer1M: 4.5, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
|
|
96
102
|
"gpt-5.3-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
103
|
+
"gpt-5.3-chat": { inputPer1M: 2, outputPer1M: 8, cacheReadPer1M: 0.5, cacheWritePer1M: 0 },
|
|
97
104
|
"gpt-5.2-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
98
105
|
"gpt-5-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
106
|
+
"gpt-5-mini": { inputPer1M: 0.3, outputPer1M: 1.2, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
|
|
107
|
+
"gpt-5.2": { inputPer1M: 2, outputPer1M: 8, cacheReadPer1M: 0.5, cacheWritePer1M: 0 },
|
|
99
108
|
"gpt-4o": { inputPer1M: 2.5, outputPer1M: 10, cacheReadPer1M: 1.25, cacheWritePer1M: 0 },
|
|
100
109
|
"gpt-4o-mini": { inputPer1M: 0.15, outputPer1M: 0.6, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
|
|
101
110
|
o1: { inputPer1M: 15, outputPer1M: 60, cacheReadPer1M: 7.5, cacheWritePer1M: 0 },
|
|
102
111
|
"o1-mini": { inputPer1M: 3, outputPer1M: 12, cacheReadPer1M: 1.5, cacheWritePer1M: 0 },
|
|
103
112
|
o3: { inputPer1M: 10, outputPer1M: 40, cacheReadPer1M: 2.5, cacheWritePer1M: 0 },
|
|
104
113
|
"o3-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.55, cacheWritePer1M: 0 },
|
|
105
|
-
"o4-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.275, cacheWritePer1M: 0 }
|
|
114
|
+
"o4-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.275, cacheWritePer1M: 0 },
|
|
115
|
+
"qwen3.6-plus": { inputPer1M: 0.8, outputPer1M: 2, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
116
|
+
"qwen3.6": { inputPer1M: 0.3, outputPer1M: 0.6, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
117
|
+
"minimax-m2.7": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
118
|
+
"minimax-m2.7-highspeed": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
119
|
+
"minimax-m1": { inputPer1M: 0.2, outputPer1M: 1.1, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
120
|
+
"grok-3": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
121
|
+
"grok-3-mini": { inputPer1M: 0.3, outputPer1M: 0.5, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
122
|
+
"glm-5.1": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
123
|
+
"glm-5": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
124
|
+
"kimi-k2": { inputPer1M: 0.6, outputPer1M: 0.6, cacheReadPer1M: 0, cacheWritePer1M: 0 }
|
|
106
125
|
};
|
|
107
126
|
});
|
|
108
127
|
|
|
@@ -253,6 +272,18 @@ function initSchema(db) {
|
|
|
253
272
|
machine_id TEXT,
|
|
254
273
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
255
274
|
);
|
|
275
|
+
|
|
276
|
+
CREATE TABLE IF NOT EXISTS billing_daily (
|
|
277
|
+
date TEXT NOT NULL,
|
|
278
|
+
provider TEXT NOT NULL,
|
|
279
|
+
description TEXT DEFAULT '',
|
|
280
|
+
cost_usd REAL NOT NULL DEFAULT 0,
|
|
281
|
+
updated_at TEXT NOT NULL,
|
|
282
|
+
PRIMARY KEY (date, provider, description)
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
CREATE INDEX IF NOT EXISTS idx_billing_date ON billing_daily(date);
|
|
286
|
+
CREATE INDEX IF NOT EXISTS idx_billing_provider ON billing_daily(provider);
|
|
256
287
|
`);
|
|
257
288
|
const cols = db.prepare(`PRAGMA table_info(requests)`).all();
|
|
258
289
|
if (!cols.some((c) => c.name === "machine_id")) {
|
|
@@ -271,11 +302,11 @@ function periodWhere(period) {
|
|
|
271
302
|
case "yesterday":
|
|
272
303
|
return `DATE(timestamp) = DATE('now', '-1 day')`;
|
|
273
304
|
case "week":
|
|
274
|
-
return `timestamp >= DATE('now', '-7 days')`;
|
|
305
|
+
return `timestamp >= DATE('now', 'weekday 0', '-7 days')`;
|
|
275
306
|
case "month":
|
|
276
|
-
return `timestamp >= DATE('now', '
|
|
307
|
+
return `timestamp >= DATE('now', 'start of month')`;
|
|
277
308
|
case "year":
|
|
278
|
-
return `timestamp >= DATE('now', '
|
|
309
|
+
return `timestamp >= DATE('now', 'start of year')`;
|
|
279
310
|
case "all":
|
|
280
311
|
return "1=1";
|
|
281
312
|
}
|
|
@@ -287,11 +318,11 @@ function sessionPeriodWhere(period) {
|
|
|
287
318
|
case "yesterday":
|
|
288
319
|
return `DATE(started_at) = DATE('now', '-1 day')`;
|
|
289
320
|
case "week":
|
|
290
|
-
return `started_at >= DATE('now', '-7 days')`;
|
|
321
|
+
return `started_at >= DATE('now', 'weekday 0', '-7 days')`;
|
|
291
322
|
case "month":
|
|
292
|
-
return `started_at >= DATE('now', '
|
|
323
|
+
return `started_at >= DATE('now', 'start of month')`;
|
|
293
324
|
case "year":
|
|
294
|
-
return `started_at >= DATE('now', '
|
|
325
|
+
return `started_at >= DATE('now', 'start of year')`;
|
|
295
326
|
case "all":
|
|
296
327
|
return "1=1";
|
|
297
328
|
}
|
|
@@ -403,19 +434,39 @@ function queryModelBreakdown(db) {
|
|
|
403
434
|
}
|
|
404
435
|
function queryProjectBreakdown(db) {
|
|
405
436
|
return db.prepare(`
|
|
437
|
+
WITH labeled AS (
|
|
438
|
+
SELECT
|
|
439
|
+
s.id,
|
|
440
|
+
s.project_path,
|
|
441
|
+
s.total_cost_usd,
|
|
442
|
+
s.started_at,
|
|
443
|
+
COALESCE(
|
|
444
|
+
NULLIF(s.project_name, ''),
|
|
445
|
+
CASE
|
|
446
|
+
WHEN s.project_path LIKE '%/%'
|
|
447
|
+
THEN substr(s.project_path, length(rtrim(s.project_path, replace(s.project_path, '/', ''))) + 1)
|
|
448
|
+
ELSE s.project_path
|
|
449
|
+
END
|
|
450
|
+
) as label
|
|
451
|
+
FROM sessions s
|
|
452
|
+
WHERE s.project_path != '' OR s.project_name != ''
|
|
453
|
+
)
|
|
406
454
|
SELECT
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
COUNT(DISTINCT
|
|
410
|
-
COUNT(r.id) as requests,
|
|
411
|
-
COALESCE(
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
455
|
+
MIN(l.project_path) as project_path,
|
|
456
|
+
l.label as project_name,
|
|
457
|
+
COUNT(DISTINCT l.id) as sessions,
|
|
458
|
+
COALESCE((SELECT COUNT(*) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)), 0) as requests,
|
|
459
|
+
COALESCE(
|
|
460
|
+
(SELECT SUM(r.cost_usd) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)),
|
|
461
|
+
SUM(l.total_cost_usd)
|
|
462
|
+
) as cost_usd,
|
|
463
|
+
COALESCE(
|
|
464
|
+
(SELECT SUM(r.input_tokens + r.output_tokens + r.cache_read_tokens + r.cache_create_tokens) FROM requests r WHERE r.session_id IN (SELECT id FROM labeled WHERE label = l.label)),
|
|
465
|
+
0
|
|
466
|
+
) as total_tokens,
|
|
467
|
+
MAX(l.started_at) as last_active
|
|
468
|
+
FROM labeled l
|
|
469
|
+
GROUP BY l.label
|
|
419
470
|
ORDER BY cost_usd DESC
|
|
420
471
|
`).all();
|
|
421
472
|
}
|
|
@@ -557,11 +608,11 @@ function deleteModelPricing(db, model) {
|
|
|
557
608
|
db.prepare(`DELETE FROM model_pricing WHERE model = ?`).run(model);
|
|
558
609
|
}
|
|
559
610
|
function seedModelPricing(db, defaults) {
|
|
560
|
-
const existing = db.prepare(`SELECT
|
|
561
|
-
if (existing.count > 0)
|
|
562
|
-
return;
|
|
611
|
+
const existing = new Set(db.prepare(`SELECT model FROM model_pricing`).all().map((r) => r.model));
|
|
563
612
|
const now = new Date().toISOString();
|
|
564
613
|
for (const [model, p] of Object.entries(defaults)) {
|
|
614
|
+
if (existing.has(model))
|
|
615
|
+
continue;
|
|
565
616
|
upsertModelPricing(db, {
|
|
566
617
|
model,
|
|
567
618
|
input_per_1m: p.inputPer1M,
|
package/package.json
CHANGED