@hasna/economy 0.2.27 → 0.2.29
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 +2 -1
- package/README.md +16 -1
- package/dist/cli/index.js +417 -189
- package/dist/db/database.d.ts +10 -5
- package/dist/db/database.d.ts.map +1 -1
- package/dist/index.js +268 -162
- package/dist/lib/accounts.d.ts.map +1 -1
- package/dist/mcp/http.d.ts +13 -0
- package/dist/mcp/http.d.ts.map +1 -0
- package/dist/mcp/index.js +846 -670
- package/dist/mcp/server.d.ts +4 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/otel/index.js +35 -43
- package/dist/server/index.js +284 -170
- package/dist/server/serve.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/db/database.d.ts
CHANGED
|
@@ -11,14 +11,19 @@ export declare function querySessions(db: Database, filter?: SessionFilter): Eco
|
|
|
11
11
|
export declare function queryTopSessions(db: Database, n?: number, agent?: string): EconomySession[];
|
|
12
12
|
export declare function querySummary(db: Database, period: Period, machine?: string, allMachines?: boolean): CostSummary;
|
|
13
13
|
export declare function queryModelBreakdown(db: Database): ModelBreakdown[];
|
|
14
|
-
export declare function queryAgentBreakdown(db: Database, period?: Period): AgentBreakdown[];
|
|
15
|
-
export declare function queryProjectBreakdown(db: Database, period?: Period): ProjectBreakdown[];
|
|
16
|
-
export declare function queryAccountBreakdown(db: Database, period?: Period): AccountBreakdown[];
|
|
17
|
-
export declare function queryDailyBreakdown(db: Database, days?: number): Array<{
|
|
14
|
+
export declare function queryAgentBreakdown(db: Database, period?: Period, machine?: string): AgentBreakdown[];
|
|
15
|
+
export declare function queryProjectBreakdown(db: Database, period?: Period, machine?: string): ProjectBreakdown[];
|
|
16
|
+
export declare function queryAccountBreakdown(db: Database, period?: Period, machine?: string): AccountBreakdown[];
|
|
17
|
+
export declare function queryDailyBreakdown(db: Database, days?: number, machine?: string): Array<{
|
|
18
18
|
date: string;
|
|
19
19
|
cost_usd: number;
|
|
20
20
|
agent: string;
|
|
21
21
|
}>;
|
|
22
|
+
export declare function queryHourlyBreakdown(db: Database, machine?: string): Array<{
|
|
23
|
+
hour: string;
|
|
24
|
+
cost_usd: number;
|
|
25
|
+
agent: string;
|
|
26
|
+
}>;
|
|
22
27
|
export declare function upsertProject(db: Database, project: EconomyProject): void;
|
|
23
28
|
export declare function getProject(db: Database, path: string): EconomyProject | null;
|
|
24
29
|
export declare function listProjects(db: Database): EconomyProject[];
|
|
@@ -70,7 +75,7 @@ export interface MachineInfo {
|
|
|
70
75
|
total_cost_usd: number;
|
|
71
76
|
last_active: string;
|
|
72
77
|
}
|
|
73
|
-
export declare function listMachines(db: Database): MachineInfo[];
|
|
78
|
+
export declare function listMachines(db: Database, period?: Period): MachineInfo[];
|
|
74
79
|
export interface DbModelPricing {
|
|
75
80
|
model: string;
|
|
76
81
|
input_per_1m: 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,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
|
@@ -565,6 +565,26 @@ function openDatabase(dbPath, skipSeed = false) {
|
|
|
565
565
|
}
|
|
566
566
|
return db;
|
|
567
567
|
}
|
|
568
|
+
function quoteSqlIdent(identifier) {
|
|
569
|
+
return `"${identifier.replace(/"/g, '""')}"`;
|
|
570
|
+
}
|
|
571
|
+
function hasColumn(db, table, column) {
|
|
572
|
+
const columns = db.prepare(`PRAGMA table_info(${quoteSqlIdent(table)})`).all();
|
|
573
|
+
return columns.some((c) => c.name === column);
|
|
574
|
+
}
|
|
575
|
+
function addColumnIfMissing(db, table, column, definition) {
|
|
576
|
+
if (hasColumn(db, table, column))
|
|
577
|
+
return false;
|
|
578
|
+
try {
|
|
579
|
+
db.exec(`ALTER TABLE ${quoteSqlIdent(table)} ADD COLUMN ${quoteSqlIdent(column)} ${definition}`);
|
|
580
|
+
return true;
|
|
581
|
+
} catch (error) {
|
|
582
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
583
|
+
if (/duplicate column name/i.test(message))
|
|
584
|
+
return true;
|
|
585
|
+
throw error;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
568
588
|
function initSchema(db) {
|
|
569
589
|
db.exec(`
|
|
570
590
|
CREATE TABLE IF NOT EXISTS requests (
|
|
@@ -735,59 +755,31 @@ function initSchema(db) {
|
|
|
735
755
|
CREATE INDEX IF NOT EXISTS idx_usage_agent_date ON usage_snapshots(agent, date);
|
|
736
756
|
CREATE INDEX IF NOT EXISTS idx_savings_date ON savings_daily(date);
|
|
737
757
|
`);
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN machine_id TEXT DEFAULT ''`);
|
|
742
|
-
}
|
|
743
|
-
if (!cols.some((c) => c.name === "cache_create_5m_tokens")) {
|
|
744
|
-
db.exec(`ALTER TABLE requests ADD COLUMN cache_create_5m_tokens INTEGER DEFAULT 0`);
|
|
758
|
+
addColumnIfMissing(db, "requests", "machine_id", `TEXT DEFAULT ''`);
|
|
759
|
+
addColumnIfMissing(db, "sessions", "machine_id", `TEXT DEFAULT ''`);
|
|
760
|
+
if (addColumnIfMissing(db, "requests", "cache_create_5m_tokens", "INTEGER DEFAULT 0")) {
|
|
745
761
|
db.exec(`UPDATE requests SET cache_create_5m_tokens = cache_create_tokens WHERE cache_create_5m_tokens = 0`);
|
|
746
762
|
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
if (
|
|
751
|
-
db.exec(`ALTER TABLE requests ADD COLUMN cost_basis TEXT DEFAULT 'estimated'`);
|
|
752
|
-
}
|
|
753
|
-
if (!cols.some((c) => c.name === "attribution_tag")) {
|
|
754
|
-
db.exec(`ALTER TABLE requests ADD COLUMN attribution_tag TEXT DEFAULT ''`);
|
|
755
|
-
}
|
|
756
|
-
if (!cols.some((c) => c.name === "updated_at")) {
|
|
757
|
-
db.exec(`ALTER TABLE requests ADD COLUMN updated_at TEXT DEFAULT ''`);
|
|
763
|
+
addColumnIfMissing(db, "requests", "cache_create_1h_tokens", "INTEGER DEFAULT 0");
|
|
764
|
+
addColumnIfMissing(db, "requests", "cost_basis", `TEXT DEFAULT 'estimated'`);
|
|
765
|
+
addColumnIfMissing(db, "requests", "attribution_tag", `TEXT DEFAULT ''`);
|
|
766
|
+
if (addColumnIfMissing(db, "requests", "updated_at", `TEXT DEFAULT ''`)) {
|
|
758
767
|
db.exec(`UPDATE requests SET updated_at = timestamp WHERE updated_at = '' OR updated_at IS NULL`);
|
|
759
768
|
}
|
|
760
|
-
|
|
761
|
-
db.exec(`ALTER TABLE requests ADD COLUMN synced_at TEXT DEFAULT ''`);
|
|
762
|
-
}
|
|
769
|
+
addColumnIfMissing(db, "requests", "synced_at", `TEXT DEFAULT ''`);
|
|
763
770
|
for (const column of ["account_key", "account_tool", "account_name", "account_email", "account_source"]) {
|
|
764
|
-
|
|
765
|
-
db.exec(`ALTER TABLE requests ADD COLUMN ${column} TEXT DEFAULT ''`);
|
|
766
|
-
}
|
|
771
|
+
addColumnIfMissing(db, "requests", column, `TEXT DEFAULT ''`);
|
|
767
772
|
}
|
|
768
|
-
|
|
769
|
-
if (
|
|
770
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN attribution_tag TEXT DEFAULT ''`);
|
|
771
|
-
}
|
|
772
|
-
if (!sessionCols.some((c) => c.name === "updated_at")) {
|
|
773
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN updated_at TEXT DEFAULT ''`);
|
|
773
|
+
addColumnIfMissing(db, "sessions", "attribution_tag", `TEXT DEFAULT ''`);
|
|
774
|
+
if (addColumnIfMissing(db, "sessions", "updated_at", `TEXT DEFAULT ''`)) {
|
|
774
775
|
db.exec(`UPDATE sessions SET updated_at = started_at WHERE updated_at = '' OR updated_at IS NULL`);
|
|
775
776
|
}
|
|
776
|
-
|
|
777
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN synced_at TEXT DEFAULT ''`);
|
|
778
|
-
}
|
|
777
|
+
addColumnIfMissing(db, "sessions", "synced_at", `TEXT DEFAULT ''`);
|
|
779
778
|
for (const column of ["account_key", "account_tool", "account_name", "account_email", "account_source"]) {
|
|
780
|
-
|
|
781
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN ${column} TEXT DEFAULT ''`);
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
const pricingCols = db.prepare(`PRAGMA table_info(model_pricing)`).all();
|
|
785
|
-
if (!pricingCols.some((c) => c.name === "cache_write_1h_per_1m")) {
|
|
786
|
-
db.exec(`ALTER TABLE model_pricing ADD COLUMN cache_write_1h_per_1m REAL NOT NULL DEFAULT 0`);
|
|
787
|
-
}
|
|
788
|
-
if (!pricingCols.some((c) => c.name === "cache_storage_per_1m_hour")) {
|
|
789
|
-
db.exec(`ALTER TABLE model_pricing ADD COLUMN cache_storage_per_1m_hour REAL NOT NULL DEFAULT 0`);
|
|
779
|
+
addColumnIfMissing(db, "sessions", column, `TEXT DEFAULT ''`);
|
|
790
780
|
}
|
|
781
|
+
addColumnIfMissing(db, "model_pricing", "cache_write_1h_per_1m", "REAL NOT NULL DEFAULT 0");
|
|
782
|
+
addColumnIfMissing(db, "model_pricing", "cache_storage_per_1m_hour", "REAL NOT NULL DEFAULT 0");
|
|
791
783
|
db.exec(`
|
|
792
784
|
CREATE INDEX IF NOT EXISTS idx_requests_machine ON requests(machine_id);
|
|
793
785
|
CREATE INDEX IF NOT EXISTS idx_sessions_machine ON sessions(machine_id);
|
|
@@ -948,17 +940,22 @@ function querySummary(db, period, machine, allMachines = false) {
|
|
|
948
940
|
const codexTotals = db.prepare(`
|
|
949
941
|
SELECT COALESCE(SUM(total_cost_usd), 0) as cost_usd,
|
|
950
942
|
COALESCE(SUM(total_tokens), 0) as tokens,
|
|
943
|
+
COALESCE(SUM(request_count), 0) as requests,
|
|
951
944
|
COUNT(*) as sessions
|
|
952
945
|
FROM sessions
|
|
953
946
|
WHERE ${sWhere}${machineClause}
|
|
954
947
|
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
955
948
|
`).get();
|
|
956
|
-
const
|
|
949
|
+
const requestSessionCount = db.prepare(`
|
|
950
|
+
SELECT COUNT(DISTINCT session_id) as sessions
|
|
951
|
+
FROM requests
|
|
952
|
+
WHERE ${rWhere}${machineClause}
|
|
953
|
+
`).get();
|
|
957
954
|
return {
|
|
958
955
|
total_usd: r.total_usd + codexTotals.cost_usd,
|
|
959
|
-
requests: r.requests,
|
|
956
|
+
requests: r.requests + codexTotals.requests,
|
|
960
957
|
tokens: r.tokens + codexTotals.tokens,
|
|
961
|
-
sessions:
|
|
958
|
+
sessions: requestSessionCount.sessions + codexTotals.sessions,
|
|
962
959
|
period
|
|
963
960
|
};
|
|
964
961
|
}
|
|
@@ -973,8 +970,10 @@ function queryModelBreakdown(db) {
|
|
|
973
970
|
FROM requests GROUP BY model, agent ORDER BY cost_usd DESC
|
|
974
971
|
`).all();
|
|
975
972
|
}
|
|
976
|
-
function queryAgentBreakdown(db, period = "all") {
|
|
973
|
+
function queryAgentBreakdown(db, period = "all", machine) {
|
|
977
974
|
const requestWhere = requestPeriodWhere(period);
|
|
975
|
+
const machineClause = machine ? " AND machine_id = ?" : "";
|
|
976
|
+
const machineParams = machine ? [machine] : [];
|
|
978
977
|
const groups = new Map;
|
|
979
978
|
const requestRows = db.prepare(`
|
|
980
979
|
SELECT agent,
|
|
@@ -990,10 +989,10 @@ function queryAgentBreakdown(db, period = "all") {
|
|
|
990
989
|
COALESCE(SUM(cost_usd), 0) as cost_usd,
|
|
991
990
|
MAX(timestamp) as last_active
|
|
992
991
|
FROM requests
|
|
993
|
-
WHERE ${requestWhere}
|
|
992
|
+
WHERE ${requestWhere}${machineClause}
|
|
994
993
|
GROUP BY agent
|
|
995
994
|
ORDER BY api_equivalent_usd DESC
|
|
996
|
-
`).all();
|
|
995
|
+
`).all(...machineParams);
|
|
997
996
|
for (const row of requestRows) {
|
|
998
997
|
groups.set(row.agent, row);
|
|
999
998
|
}
|
|
@@ -1006,10 +1005,10 @@ function queryAgentBreakdown(db, period = "all") {
|
|
|
1006
1005
|
COALESCE(SUM(total_cost_usd), 0) as cost_usd,
|
|
1007
1006
|
MAX(started_at) as last_active
|
|
1008
1007
|
FROM sessions
|
|
1009
|
-
WHERE ${sessionWhere}
|
|
1008
|
+
WHERE ${sessionWhere}${machineClause}
|
|
1010
1009
|
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
1011
1010
|
GROUP BY agent
|
|
1012
|
-
`).all();
|
|
1011
|
+
`).all(...machineParams);
|
|
1013
1012
|
for (const row of sessionOnlyRows) {
|
|
1014
1013
|
const existing = groups.get(row.agent) ?? {
|
|
1015
1014
|
agent: row.agent,
|
|
@@ -1086,14 +1085,20 @@ function labelForPath(projectPath, projectName) {
|
|
|
1086
1085
|
function groupKeyForPath(projectPath, projectName) {
|
|
1087
1086
|
return labelForPath(projectPath, projectName).trim().toLowerCase();
|
|
1088
1087
|
}
|
|
1089
|
-
function queryProjectBreakdown(db, period = "all") {
|
|
1088
|
+
function queryProjectBreakdown(db, period = "all", machine) {
|
|
1090
1089
|
const requestWhere = requestPeriodWhere(period);
|
|
1091
1090
|
const sessionWhere = sessionPeriodWhere(period);
|
|
1091
|
+
const sessionMachineClause = machine ? " AND (machine_id = ? OR id IN (SELECT DISTINCT session_id FROM requests WHERE machine_id = ?))" : "";
|
|
1092
|
+
const requestMachineClause = machine ? " AND machine_id = ?" : "";
|
|
1093
|
+
const sessionMachineParams = machine ? [machine, machine] : [];
|
|
1094
|
+
const requestMachineParams = machine ? [machine] : [];
|
|
1095
|
+
const sessionOnlyMachineClause = machine ? " AND machine_id = ?" : "";
|
|
1096
|
+
const sessionOnlyMachineParams = machine ? [machine] : [];
|
|
1092
1097
|
const sessions = db.prepare(`
|
|
1093
1098
|
SELECT id, project_path, project_name, total_cost_usd, started_at
|
|
1094
1099
|
FROM sessions
|
|
1095
|
-
WHERE project_path != '' OR project_name != ''
|
|
1096
|
-
`).all();
|
|
1100
|
+
WHERE (project_path != '' OR project_name != '')${sessionMachineClause}
|
|
1101
|
+
`).all(...sessionMachineParams);
|
|
1097
1102
|
const groups = new Map;
|
|
1098
1103
|
for (const s of sessions) {
|
|
1099
1104
|
const label = labelForPath(s.project_path, s.project_name);
|
|
@@ -1119,7 +1124,8 @@ function queryProjectBreakdown(db, period = "all") {
|
|
|
1119
1124
|
FROM requests
|
|
1120
1125
|
WHERE session_id IN (${placeholders})
|
|
1121
1126
|
AND ${requestWhere}
|
|
1122
|
-
|
|
1127
|
+
${requestMachineClause}
|
|
1128
|
+
`).get(...g.sessionIds, ...requestMachineParams) : { sessions: 0, requests: 0, cost_usd: 0, total_tokens: 0, last_active: null };
|
|
1123
1129
|
const sessionOnlyStats = placeholders.length ? db.prepare(`
|
|
1124
1130
|
SELECT
|
|
1125
1131
|
COUNT(*) as sessions,
|
|
@@ -1130,8 +1136,9 @@ function queryProjectBreakdown(db, period = "all") {
|
|
|
1130
1136
|
FROM sessions
|
|
1131
1137
|
WHERE id IN (${placeholders})
|
|
1132
1138
|
AND ${sessionWhere}
|
|
1139
|
+
${sessionOnlyMachineClause}
|
|
1133
1140
|
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
1134
|
-
`).get(...g.sessionIds) : { sessions: 0, requests: 0, total_tokens: 0, cost_usd: 0, last_active: null };
|
|
1141
|
+
`).get(...g.sessionIds, ...sessionOnlyMachineParams) : { sessions: 0, requests: 0, total_tokens: 0, cost_usd: 0, last_active: null };
|
|
1135
1142
|
const totalSessions = reqStats.sessions + sessionOnlyStats.sessions;
|
|
1136
1143
|
if (totalSessions === 0)
|
|
1137
1144
|
continue;
|
|
@@ -1149,107 +1156,167 @@ function queryProjectBreakdown(db, period = "all") {
|
|
|
1149
1156
|
result.sort((a, b) => b.cost_usd - a.cost_usd);
|
|
1150
1157
|
return result;
|
|
1151
1158
|
}
|
|
1152
|
-
function
|
|
1159
|
+
function normalizeAccountEmail(email) {
|
|
1160
|
+
return (email ?? "").trim().toLowerCase();
|
|
1161
|
+
}
|
|
1162
|
+
function accountIdentityKey(agent, accountKey, accountName, accountEmail) {
|
|
1163
|
+
const identityAgent = (agent || "").trim();
|
|
1164
|
+
const normalizedEmail = normalizeAccountEmail(accountEmail);
|
|
1165
|
+
if (identityAgent && normalizedEmail)
|
|
1166
|
+
return `${identityAgent}:${normalizedEmail}`;
|
|
1167
|
+
if (identityAgent && accountName)
|
|
1168
|
+
return `${identityAgent}:${accountName}`;
|
|
1169
|
+
if (accountKey)
|
|
1170
|
+
return accountKey;
|
|
1171
|
+
return identityAgent ? `${identityAgent}:unknown` : "";
|
|
1172
|
+
}
|
|
1173
|
+
function addAccountBreakdownRow(groups, row, sessionOnly) {
|
|
1174
|
+
const agent = row.agent || row.account_tool;
|
|
1175
|
+
const email = normalizeAccountEmail(row.account_email);
|
|
1176
|
+
const accountName = row.account_name || email || row.account_key;
|
|
1177
|
+
const key = accountIdentityKey(agent, row.account_key, accountName, email);
|
|
1178
|
+
if (!key)
|
|
1179
|
+
return;
|
|
1180
|
+
const group = groups.get(key) ?? {
|
|
1181
|
+
account_key: key,
|
|
1182
|
+
account_tool: agent,
|
|
1183
|
+
account_name: accountName,
|
|
1184
|
+
account_email: email || null,
|
|
1185
|
+
account_source: row.account_source || "unknown",
|
|
1186
|
+
sessionIds: new Set,
|
|
1187
|
+
requests: 0,
|
|
1188
|
+
total_tokens: 0,
|
|
1189
|
+
api_equivalent_usd: 0,
|
|
1190
|
+
metered_api_usd: 0,
|
|
1191
|
+
subscription_included_usd: 0,
|
|
1192
|
+
estimated_usd: 0,
|
|
1193
|
+
unknown_usd: 0,
|
|
1194
|
+
last_active: ""
|
|
1195
|
+
};
|
|
1196
|
+
if (!group.account_email && email)
|
|
1197
|
+
group.account_email = email;
|
|
1198
|
+
if (!group.account_name && accountName)
|
|
1199
|
+
group.account_name = accountName;
|
|
1200
|
+
if ((!group.account_source || group.account_source === "unknown") && row.account_source && row.account_source !== "unknown") {
|
|
1201
|
+
group.account_source = row.account_source;
|
|
1202
|
+
}
|
|
1203
|
+
if (row.session_id)
|
|
1204
|
+
group.sessionIds.add(row.session_id);
|
|
1205
|
+
group.requests += row.requests;
|
|
1206
|
+
group.total_tokens += row.total_tokens;
|
|
1207
|
+
group.api_equivalent_usd += row.cost_usd;
|
|
1208
|
+
if (sessionOnly) {
|
|
1209
|
+
group.estimated_usd += row.cost_usd;
|
|
1210
|
+
} else if (row.cost_basis === "metered_api") {
|
|
1211
|
+
group.metered_api_usd += row.cost_usd;
|
|
1212
|
+
} else if (row.cost_basis === "subscription_included") {
|
|
1213
|
+
group.subscription_included_usd += row.cost_usd;
|
|
1214
|
+
} else if (row.cost_basis === "unknown") {
|
|
1215
|
+
group.unknown_usd += row.cost_usd;
|
|
1216
|
+
} else {
|
|
1217
|
+
group.estimated_usd += row.cost_usd;
|
|
1218
|
+
}
|
|
1219
|
+
if (!group.last_active || row.last_active > group.last_active)
|
|
1220
|
+
group.last_active = row.last_active;
|
|
1221
|
+
groups.set(key, group);
|
|
1222
|
+
}
|
|
1223
|
+
function queryAccountBreakdown(db, period = "all", machine) {
|
|
1153
1224
|
const requestWhere = requestPeriodWhere(period);
|
|
1154
1225
|
const sessionWhere = sessionPeriodWhere(period);
|
|
1155
|
-
const
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
WHERE account_key != '' OR account_tool != '' OR account_name != '' OR account_email != ''
|
|
1160
|
-
`).all();
|
|
1226
|
+
const requestMachineClause = machine ? " AND r.machine_id = ?" : "";
|
|
1227
|
+
const sessionMachineClause = machine ? " AND s.machine_id = ?" : "";
|
|
1228
|
+
const requestMachineParams = machine ? [machine] : [];
|
|
1229
|
+
const sessionMachineParams = machine ? [machine] : [];
|
|
1161
1230
|
const groups = new Map;
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
account_email: group.account_email,
|
|
1228
|
-
account_source: group.account_source,
|
|
1229
|
-
sessions: sessionsTotal,
|
|
1230
|
-
requests: reqStats.requests + sessionOnlyStats.requests,
|
|
1231
|
-
total_tokens: reqStats.total_tokens + sessionOnlyStats.total_tokens,
|
|
1232
|
-
api_equivalent_usd: apiEquivalentUsd,
|
|
1233
|
-
billable_usd: billableUsd,
|
|
1234
|
-
metered_api_usd: reqStats.metered_api_usd,
|
|
1235
|
-
subscription_included_usd: reqStats.subscription_included_usd,
|
|
1236
|
-
estimated_usd: estimatedUsd,
|
|
1237
|
-
unknown_usd: reqStats.unknown_usd,
|
|
1238
|
-
cost_usd: apiEquivalentUsd,
|
|
1239
|
-
last_active: lastActive
|
|
1240
|
-
});
|
|
1241
|
-
}
|
|
1231
|
+
const requestRows = db.prepare(`
|
|
1232
|
+
SELECT
|
|
1233
|
+
r.session_id as session_id,
|
|
1234
|
+
COALESCE(NULLIF(r.agent, ''), NULLIF(s.agent, ''), '') as agent,
|
|
1235
|
+
COALESCE(NULLIF(r.account_key, ''), NULLIF(s.account_key, ''), '') as account_key,
|
|
1236
|
+
COALESCE(NULLIF(r.account_tool, ''), NULLIF(s.account_tool, ''), '') as account_tool,
|
|
1237
|
+
COALESCE(NULLIF(r.account_name, ''), NULLIF(s.account_name, ''), '') as account_name,
|
|
1238
|
+
COALESCE(NULLIF(r.account_email, ''), NULLIF(s.account_email, ''), '') as account_email,
|
|
1239
|
+
COALESCE(NULLIF(r.account_source, ''), NULLIF(s.account_source, ''), 'unknown') as account_source,
|
|
1240
|
+
1 as requests,
|
|
1241
|
+
COALESCE(r.input_tokens + r.output_tokens + r.cache_read_tokens + r.cache_create_tokens, 0) as total_tokens,
|
|
1242
|
+
COALESCE(r.cost_usd, 0) as cost_usd,
|
|
1243
|
+
COALESCE(NULLIF(r.cost_basis, ''), 'estimated') as cost_basis,
|
|
1244
|
+
r.timestamp as last_active
|
|
1245
|
+
FROM requests r
|
|
1246
|
+
LEFT JOIN sessions s ON s.id = r.session_id
|
|
1247
|
+
WHERE ${requestWhere}${requestMachineClause}
|
|
1248
|
+
AND (
|
|
1249
|
+
COALESCE(NULLIF(r.account_key, ''), NULLIF(s.account_key, ''), '') != ''
|
|
1250
|
+
OR COALESCE(NULLIF(r.account_tool, ''), NULLIF(s.account_tool, ''), '') != ''
|
|
1251
|
+
OR COALESCE(NULLIF(r.account_name, ''), NULLIF(s.account_name, ''), '') != ''
|
|
1252
|
+
OR COALESCE(NULLIF(r.account_email, ''), NULLIF(s.account_email, ''), '') != ''
|
|
1253
|
+
)
|
|
1254
|
+
`).all(...requestMachineParams);
|
|
1255
|
+
for (const row of requestRows)
|
|
1256
|
+
addAccountBreakdownRow(groups, row, false);
|
|
1257
|
+
const sessionOnlyRows = db.prepare(`
|
|
1258
|
+
SELECT
|
|
1259
|
+
s.id as session_id,
|
|
1260
|
+
s.agent as agent,
|
|
1261
|
+
s.account_key as account_key,
|
|
1262
|
+
s.account_tool as account_tool,
|
|
1263
|
+
s.account_name as account_name,
|
|
1264
|
+
s.account_email as account_email,
|
|
1265
|
+
COALESCE(NULLIF(s.account_source, ''), 'unknown') as account_source,
|
|
1266
|
+
COALESCE(s.request_count, 0) as requests,
|
|
1267
|
+
COALESCE(s.total_tokens, 0) as total_tokens,
|
|
1268
|
+
COALESCE(s.total_cost_usd, 0) as cost_usd,
|
|
1269
|
+
'estimated' as cost_basis,
|
|
1270
|
+
s.started_at as last_active
|
|
1271
|
+
FROM sessions s
|
|
1272
|
+
WHERE ${sessionWhere}${sessionMachineClause}
|
|
1273
|
+
AND s.id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
1274
|
+
AND (s.account_key != '' OR s.account_tool != '' OR s.account_name != '' OR s.account_email != '')
|
|
1275
|
+
`).all(...sessionMachineParams);
|
|
1276
|
+
for (const row of sessionOnlyRows)
|
|
1277
|
+
addAccountBreakdownRow(groups, row, true);
|
|
1278
|
+
const result = [...groups.values()].map((group) => ({
|
|
1279
|
+
account_key: group.account_key,
|
|
1280
|
+
account_tool: group.account_tool,
|
|
1281
|
+
account_name: group.account_name,
|
|
1282
|
+
account_email: group.account_email,
|
|
1283
|
+
account_source: group.account_source,
|
|
1284
|
+
sessions: group.sessionIds.size,
|
|
1285
|
+
requests: group.requests,
|
|
1286
|
+
total_tokens: group.total_tokens,
|
|
1287
|
+
api_equivalent_usd: group.api_equivalent_usd,
|
|
1288
|
+
billable_usd: group.metered_api_usd,
|
|
1289
|
+
metered_api_usd: group.metered_api_usd,
|
|
1290
|
+
subscription_included_usd: group.subscription_included_usd,
|
|
1291
|
+
estimated_usd: group.estimated_usd,
|
|
1292
|
+
unknown_usd: group.unknown_usd,
|
|
1293
|
+
cost_usd: group.api_equivalent_usd,
|
|
1294
|
+
last_active: group.last_active
|
|
1295
|
+
}));
|
|
1242
1296
|
result.sort((a, b) => b.cost_usd - a.cost_usd);
|
|
1243
1297
|
return result;
|
|
1244
1298
|
}
|
|
1245
|
-
function queryDailyBreakdown(db, days = 30) {
|
|
1299
|
+
function queryDailyBreakdown(db, days = 30, machine) {
|
|
1300
|
+
const machineClause = machine ? " AND machine_id = ?" : "";
|
|
1301
|
+
const params = machine ? [`-${days}`, machine] : [`-${days}`];
|
|
1246
1302
|
return db.prepare(`
|
|
1247
1303
|
SELECT DATE(timestamp) as date, agent, COALESCE(SUM(cost_usd), 0) as cost_usd
|
|
1248
1304
|
FROM requests
|
|
1249
|
-
WHERE timestamp >= DATE('now', ? || ' days')
|
|
1305
|
+
WHERE timestamp >= DATE('now', ? || ' days')${machineClause}
|
|
1250
1306
|
GROUP BY DATE(timestamp), agent
|
|
1251
1307
|
ORDER BY date ASC
|
|
1252
|
-
`).all(
|
|
1308
|
+
`).all(...params);
|
|
1309
|
+
}
|
|
1310
|
+
function queryHourlyBreakdown(db, machine) {
|
|
1311
|
+
const machineClause = machine ? " AND machine_id = ?" : "";
|
|
1312
|
+
const params = machine ? [machine] : [];
|
|
1313
|
+
return db.prepare(`
|
|
1314
|
+
SELECT STRFTIME('%H', timestamp) as hour, agent, COALESCE(SUM(cost_usd), 0) as cost_usd
|
|
1315
|
+
FROM requests
|
|
1316
|
+
WHERE DATE(timestamp) = DATE('now')${machineClause}
|
|
1317
|
+
GROUP BY STRFTIME('%H', timestamp), agent
|
|
1318
|
+
ORDER BY hour ASC
|
|
1319
|
+
`).all(...params);
|
|
1253
1320
|
}
|
|
1254
1321
|
function upsertProject(db, project) {
|
|
1255
1322
|
db.prepare(`
|
|
@@ -1378,17 +1445,48 @@ function queryBillingSummary(db, period) {
|
|
|
1378
1445
|
}
|
|
1379
1446
|
return { total_usd: total, by_provider };
|
|
1380
1447
|
}
|
|
1381
|
-
function listMachines(db) {
|
|
1448
|
+
function listMachines(db, period = "all") {
|
|
1449
|
+
const rWhere = requestPeriodWhere(period);
|
|
1450
|
+
const sWhere = sessionPeriodWhere(period);
|
|
1382
1451
|
return db.prepare(`
|
|
1452
|
+
WITH request_stats AS (
|
|
1453
|
+
SELECT
|
|
1454
|
+
machine_id,
|
|
1455
|
+
COUNT(DISTINCT session_id) as sessions,
|
|
1456
|
+
COUNT(*) as requests,
|
|
1457
|
+
COALESCE(SUM(cost_usd), 0) as total_cost_usd,
|
|
1458
|
+
MAX(timestamp) as last_active
|
|
1459
|
+
FROM requests
|
|
1460
|
+
WHERE machine_id != ''
|
|
1461
|
+
AND ${rWhere}
|
|
1462
|
+
GROUP BY machine_id
|
|
1463
|
+
),
|
|
1464
|
+
session_only_stats AS (
|
|
1465
|
+
SELECT
|
|
1466
|
+
machine_id,
|
|
1467
|
+
COUNT(*) as sessions,
|
|
1468
|
+
COALESCE(SUM(request_count), 0) as requests,
|
|
1469
|
+
COALESCE(SUM(total_cost_usd), 0) as total_cost_usd,
|
|
1470
|
+
MAX(started_at) as last_active
|
|
1471
|
+
FROM sessions
|
|
1472
|
+
WHERE machine_id != ''
|
|
1473
|
+
AND ${sWhere}
|
|
1474
|
+
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
1475
|
+
GROUP BY machine_id
|
|
1476
|
+
),
|
|
1477
|
+
combined AS (
|
|
1478
|
+
SELECT * FROM request_stats
|
|
1479
|
+
UNION ALL
|
|
1480
|
+
SELECT * FROM session_only_stats
|
|
1481
|
+
)
|
|
1383
1482
|
SELECT
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
COALESCE((
|
|
1387
|
-
COALESCE(SUM(
|
|
1388
|
-
MAX(
|
|
1389
|
-
FROM
|
|
1390
|
-
|
|
1391
|
-
GROUP BY s.machine_id
|
|
1483
|
+
machine_id,
|
|
1484
|
+
COALESCE(SUM(sessions), 0) as sessions,
|
|
1485
|
+
COALESCE(SUM(requests), 0) as requests,
|
|
1486
|
+
COALESCE(SUM(total_cost_usd), 0) as total_cost_usd,
|
|
1487
|
+
MAX(last_active) as last_active
|
|
1488
|
+
FROM combined
|
|
1489
|
+
GROUP BY machine_id
|
|
1392
1490
|
ORDER BY total_cost_usd DESC
|
|
1393
1491
|
`).all();
|
|
1394
1492
|
}
|
|
@@ -2087,18 +2185,22 @@ var AGENT_ACCOUNT_TOOLS = {
|
|
|
2087
2185
|
pi: ["pi"],
|
|
2088
2186
|
hermes: ["hermes"]
|
|
2089
2187
|
};
|
|
2090
|
-
function
|
|
2091
|
-
return
|
|
2188
|
+
function normalizeEmail(email) {
|
|
2189
|
+
return (email ?? "").trim().toLowerCase();
|
|
2190
|
+
}
|
|
2191
|
+
function accountKey(tool, name, email) {
|
|
2192
|
+
const normalizedEmail = normalizeEmail(email);
|
|
2193
|
+
return `${tool}:${normalizedEmail || name}`;
|
|
2092
2194
|
}
|
|
2093
2195
|
function normalizeDir(value) {
|
|
2094
2196
|
return value.replace(/\/+$/, "");
|
|
2095
2197
|
}
|
|
2096
2198
|
function fromProfile(profile, source) {
|
|
2097
2199
|
return {
|
|
2098
|
-
account_key: accountKey(profile.tool, profile.name),
|
|
2200
|
+
account_key: accountKey(profile.tool, profile.name, profile.email),
|
|
2099
2201
|
account_tool: profile.tool,
|
|
2100
2202
|
account_name: profile.name,
|
|
2101
|
-
...profile.email ? { account_email: profile.email } : {},
|
|
2203
|
+
...profile.email ? { account_email: normalizeEmail(profile.email) } : {},
|
|
2102
2204
|
account_source: source
|
|
2103
2205
|
};
|
|
2104
2206
|
}
|
|
@@ -2110,10 +2212,12 @@ function fromOverride(raw, agent) {
|
|
|
2110
2212
|
const [tool, name] = value.includes(":") ? value.split(":", 2) : [candidateTool, value];
|
|
2111
2213
|
if (!tool || !name)
|
|
2112
2214
|
return null;
|
|
2215
|
+
const email = name.includes("@") ? normalizeEmail(name) : undefined;
|
|
2113
2216
|
return {
|
|
2114
|
-
account_key: accountKey(tool, name),
|
|
2217
|
+
account_key: accountKey(tool, name, email),
|
|
2115
2218
|
account_tool: tool,
|
|
2116
2219
|
account_name: name,
|
|
2220
|
+
...email ? { account_email: email } : {},
|
|
2117
2221
|
account_source: "override"
|
|
2118
2222
|
};
|
|
2119
2223
|
}
|
|
@@ -2126,11 +2230,12 @@ function envOverride(agent, env) {
|
|
|
2126
2230
|
const name = env[`ECONOMY_${agentPrefix}_ACCOUNT_NAME`] ?? env["ECONOMY_ACCOUNT_NAME"];
|
|
2127
2231
|
if (!tool || !name)
|
|
2128
2232
|
return null;
|
|
2233
|
+
const email = normalizeEmail(env[`ECONOMY_${agentPrefix}_ACCOUNT_EMAIL`] ?? env["ECONOMY_ACCOUNT_EMAIL"]);
|
|
2129
2234
|
return {
|
|
2130
|
-
account_key: accountKey(tool, name),
|
|
2235
|
+
account_key: accountKey(tool, name, email),
|
|
2131
2236
|
account_tool: tool,
|
|
2132
2237
|
account_name: name,
|
|
2133
|
-
account_email:
|
|
2238
|
+
...email ? { account_email: email } : {},
|
|
2134
2239
|
account_source: "override"
|
|
2135
2240
|
};
|
|
2136
2241
|
}
|
|
@@ -2774,6 +2879,7 @@ export {
|
|
|
2774
2879
|
queryRequestsSince,
|
|
2775
2880
|
queryProjectBreakdown,
|
|
2776
2881
|
queryModelBreakdown,
|
|
2882
|
+
queryHourlyBreakdown,
|
|
2777
2883
|
queryDailyBreakdown,
|
|
2778
2884
|
queryBillingSummary,
|
|
2779
2885
|
queryAgentBreakdown,
|