@oh-my-pi/omp-stats 14.5.2 → 14.5.5
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/package.json +3 -3
- package/src/db.ts +108 -7
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/omp-stats",
|
|
4
|
-
"version": "14.5.
|
|
4
|
+
"version": "14.5.5",
|
|
5
5
|
"description": "Local observability dashboard for pi AI usage statistics",
|
|
6
6
|
"homepage": "https://github.com/can1357/oh-my-pi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"fmt": "biome format --write ."
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@oh-my-pi/pi-ai": "14.5.
|
|
41
|
-
"@oh-my-pi/pi-utils": "14.5.
|
|
40
|
+
"@oh-my-pi/pi-ai": "14.5.5",
|
|
41
|
+
"@oh-my-pi/pi-utils": "14.5.5",
|
|
42
42
|
"@tailwindcss/node": "^4.2.4",
|
|
43
43
|
"chart.js": "^4.5.1",
|
|
44
44
|
"date-fns": "^4.1.0",
|
package/src/db.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Database } from "bun:sqlite";
|
|
2
2
|
import * as fs from "node:fs/promises";
|
|
3
|
+
import { type GeneratedProvider, getBundledModel, type Usage } from "@oh-my-pi/pi-ai";
|
|
3
4
|
import { getConfigRootDir, getStatsDbPath } from "@oh-my-pi/pi-utils";
|
|
4
5
|
import type {
|
|
5
6
|
AggregatedStats,
|
|
@@ -12,7 +13,19 @@ import type {
|
|
|
12
13
|
TimeSeriesPoint,
|
|
13
14
|
} from "./types";
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
type ModelCost = { input: number; output: number; cacheRead: number; cacheWrite: number };
|
|
17
|
+
type UsageCost = Usage["cost"];
|
|
18
|
+
type CostTokens = Pick<Usage, "input" | "output" | "cacheRead" | "cacheWrite">;
|
|
19
|
+
|
|
20
|
+
interface CostBackfillRow {
|
|
21
|
+
id: number;
|
|
22
|
+
provider: string;
|
|
23
|
+
model: string;
|
|
24
|
+
input_tokens: number;
|
|
25
|
+
output_tokens: number;
|
|
26
|
+
cache_read_tokens: number;
|
|
27
|
+
cache_write_tokens: number;
|
|
28
|
+
}
|
|
16
29
|
|
|
17
30
|
let db: Database | null = null;
|
|
18
31
|
|
|
@@ -25,7 +38,7 @@ export async function initDb(): Promise<Database> {
|
|
|
25
38
|
// Ensure directory exists
|
|
26
39
|
await fs.mkdir(getConfigRootDir(), { recursive: true });
|
|
27
40
|
|
|
28
|
-
db = new Database(
|
|
41
|
+
db = new Database(getStatsDbPath());
|
|
29
42
|
db.exec("PRAGMA journal_mode = WAL");
|
|
30
43
|
|
|
31
44
|
// Create tables
|
|
@@ -74,9 +87,96 @@ export async function initDb(): Promise<Database> {
|
|
|
74
87
|
db.exec("ALTER TABLE messages ADD COLUMN premium_requests REAL NOT NULL DEFAULT 0");
|
|
75
88
|
}
|
|
76
89
|
db.exec("UPDATE messages SET premium_requests = 0 WHERE premium_requests IS NULL");
|
|
90
|
+
backfillMissingCatalogCosts(db);
|
|
77
91
|
return db;
|
|
78
92
|
}
|
|
79
93
|
|
|
94
|
+
function hasBillableCost(cost: ModelCost): boolean {
|
|
95
|
+
return cost.input !== 0 || cost.output !== 0 || cost.cacheRead !== 0 || cost.cacheWrite !== 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function getBundledModelCost(provider: string, modelId: string): ModelCost | null {
|
|
99
|
+
const model = getBundledModel(provider as GeneratedProvider, modelId);
|
|
100
|
+
return model?.cost ?? null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function getCatalogCost(provider: string, modelId: string): ModelCost | null {
|
|
104
|
+
const primaryCost = getBundledModelCost(provider, modelId);
|
|
105
|
+
if (primaryCost && hasBillableCost(primaryCost)) {
|
|
106
|
+
return primaryCost;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (provider === "openai-codex") {
|
|
110
|
+
const openAICost = getBundledModelCost("openai", modelId);
|
|
111
|
+
if (openAICost && hasBillableCost(openAICost)) {
|
|
112
|
+
return openAICost;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function calculateCatalogCost(provider: string, modelId: string, tokens: CostTokens): UsageCost | null {
|
|
120
|
+
const cost = getCatalogCost(provider, modelId);
|
|
121
|
+
if (!cost) return null;
|
|
122
|
+
|
|
123
|
+
const input = (cost.input / 1_000_000) * tokens.input;
|
|
124
|
+
const output = (cost.output / 1_000_000) * tokens.output;
|
|
125
|
+
const cacheRead = (cost.cacheRead / 1_000_000) * tokens.cacheRead;
|
|
126
|
+
const cacheWrite = (cost.cacheWrite / 1_000_000) * tokens.cacheWrite;
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
input,
|
|
130
|
+
output,
|
|
131
|
+
cacheRead,
|
|
132
|
+
cacheWrite,
|
|
133
|
+
total: input + output + cacheRead + cacheWrite,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function resolveStoredCost(stats: MessageStats): UsageCost {
|
|
138
|
+
if (stats.usage.cost.total !== 0) {
|
|
139
|
+
return stats.usage.cost;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return calculateCatalogCost(stats.provider, stats.model, stats.usage) ?? stats.usage.cost;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function backfillMissingCatalogCosts(database: Database): void {
|
|
146
|
+
const rows = database
|
|
147
|
+
.prepare(`
|
|
148
|
+
SELECT id, provider, model, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens
|
|
149
|
+
FROM messages
|
|
150
|
+
WHERE cost_total = 0 AND total_tokens > 0
|
|
151
|
+
`)
|
|
152
|
+
.all() as CostBackfillRow[];
|
|
153
|
+
|
|
154
|
+
if (rows.length === 0) return;
|
|
155
|
+
|
|
156
|
+
const update = database.prepare(`
|
|
157
|
+
UPDATE messages
|
|
158
|
+
SET cost_input = ?, cost_output = ?, cost_cache_read = ?, cost_cache_write = ?, cost_total = ?
|
|
159
|
+
WHERE id = ?
|
|
160
|
+
`);
|
|
161
|
+
|
|
162
|
+
const applyBackfill = database.transaction(() => {
|
|
163
|
+
for (const row of rows) {
|
|
164
|
+
const cost = calculateCatalogCost(row.provider, row.model, {
|
|
165
|
+
input: row.input_tokens,
|
|
166
|
+
output: row.output_tokens,
|
|
167
|
+
cacheRead: row.cache_read_tokens,
|
|
168
|
+
cacheWrite: row.cache_write_tokens,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
if (!cost || cost.total === 0) continue;
|
|
172
|
+
|
|
173
|
+
update.run(cost.input, cost.output, cost.cacheRead, cost.cacheWrite, cost.total, row.id);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
applyBackfill();
|
|
178
|
+
}
|
|
179
|
+
|
|
80
180
|
/**
|
|
81
181
|
* Get the stored offset for a session file.
|
|
82
182
|
*/
|
|
@@ -120,6 +220,7 @@ export function insertMessageStats(stats: MessageStats[]): number {
|
|
|
120
220
|
let inserted = 0;
|
|
121
221
|
const insert = db.transaction(() => {
|
|
122
222
|
for (const s of stats) {
|
|
223
|
+
const cost = resolveStoredCost(s);
|
|
123
224
|
const result = stmt.run(
|
|
124
225
|
s.sessionFile,
|
|
125
226
|
s.entryId,
|
|
@@ -138,11 +239,11 @@ export function insertMessageStats(stats: MessageStats[]): number {
|
|
|
138
239
|
s.usage.cacheWrite,
|
|
139
240
|
s.usage.totalTokens,
|
|
140
241
|
s.usage.premiumRequests ?? 0,
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
242
|
+
cost.input,
|
|
243
|
+
cost.output,
|
|
244
|
+
cost.cacheRead,
|
|
245
|
+
cost.cacheWrite,
|
|
246
|
+
cost.total,
|
|
146
247
|
);
|
|
147
248
|
if (result.changes > 0) inserted++;
|
|
148
249
|
}
|