@oh-my-pi/omp-stats 18.0.3 → 18.0.4
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/CHANGELOG.md +6 -0
- package/README.md +4 -2
- package/dist/client/index.js +84 -84
- package/dist/types/client/data/formatters.d.ts +5 -0
- package/dist/types/client/data/view-models.d.ts +1 -0
- package/dist/types/db.d.ts +3 -2
- package/dist/types/shared-types.d.ts +10 -0
- package/package.json +4 -4
- package/src/client/data/formatters.ts +13 -0
- package/src/client/data/view-models.ts +3 -0
- package/src/client/routes/CostsRoute.tsx +28 -10
- package/src/client/routes/ErrorsRoute.tsx +5 -5
- package/src/client/routes/ModelsRoute.tsx +3 -2
- package/src/client/routes/OverviewRoute.tsx +6 -6
- package/src/client/routes/ProjectsRoute.tsx +7 -5
- package/src/client/routes/ProvidersRoute.tsx +30 -5
- package/src/client/routes/RequestsRoute.tsx +5 -5
- package/src/client/routes/ToolsRoute.tsx +23 -11
- package/src/client/ui/MetricCluster.tsx +7 -3
- package/src/client/ui/RequestDrawer.tsx +3 -3
- package/src/db.ts +107 -29
- package/src/index.ts +10 -9
- package/src/shared-types.ts +10 -0
package/src/db.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { Database } from "bun:sqlite";
|
|
2
2
|
import * as fs from "node:fs/promises";
|
|
3
3
|
import type { Usage } from "@oh-my-pi/pi-ai";
|
|
4
|
-
import
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
calculateUncachedInputCost,
|
|
6
|
+
calculateUsageCost,
|
|
7
|
+
type GeneratedProvider,
|
|
8
|
+
getBundledModel,
|
|
9
|
+
} from "@oh-my-pi/pi-catalog/models";
|
|
10
|
+
import type { ModelCost } from "@oh-my-pi/pi-catalog/types";
|
|
6
11
|
import { getConfigRootDir, getStatsDbPath } from "@oh-my-pi/pi-utils";
|
|
7
12
|
import { classifyAgentType } from "./parser";
|
|
8
13
|
import type {
|
|
@@ -31,9 +36,8 @@ import type {
|
|
|
31
36
|
UserMessageStats,
|
|
32
37
|
} from "./types";
|
|
33
38
|
|
|
34
|
-
type ModelCost = { input: number; output: number; cacheRead: number; cacheWrite: number };
|
|
35
39
|
type UsageCost = Usage["cost"];
|
|
36
|
-
type CostTokens = Pick<Usage, "input" | "output" | "cacheRead" | "cacheWrite" | "orchestration">;
|
|
40
|
+
type CostTokens = Pick<Usage, "input" | "output" | "cacheRead" | "cacheWrite" | "orchestration" | "cttl">;
|
|
37
41
|
|
|
38
42
|
const ZERO_USAGE_COST: UsageCost = {
|
|
39
43
|
input: 0,
|
|
@@ -43,6 +47,9 @@ const ZERO_USAGE_COST: UsageCost = {
|
|
|
43
47
|
total: 0,
|
|
44
48
|
};
|
|
45
49
|
|
|
50
|
+
const UNPRICED_XAI_OAUTH_SQL =
|
|
51
|
+
"CASE WHEN provider = 'xai-oauth' AND total_tokens > 0 AND cost_total = 0 THEN 1 ELSE 0 END";
|
|
52
|
+
|
|
46
53
|
interface CostBackfillRow {
|
|
47
54
|
id: number;
|
|
48
55
|
provider: string;
|
|
@@ -71,6 +78,7 @@ interface AggregatedStatsRow {
|
|
|
71
78
|
total_cache_write_tokens: number | null;
|
|
72
79
|
total_premium_requests: number | null;
|
|
73
80
|
total_cost: number | null;
|
|
81
|
+
unpriced_requests: number | null;
|
|
74
82
|
total_cached_prompt_cost: number | null;
|
|
75
83
|
total_no_cache_input_cost: number | null;
|
|
76
84
|
avg_duration: number | null;
|
|
@@ -89,6 +97,19 @@ interface FolderStatsRow extends AggregatedStatsRow {
|
|
|
89
97
|
folder: string;
|
|
90
98
|
}
|
|
91
99
|
|
|
100
|
+
interface CostTimeSeriesRow {
|
|
101
|
+
bucket: number;
|
|
102
|
+
model: string;
|
|
103
|
+
provider: string;
|
|
104
|
+
cost: number | null;
|
|
105
|
+
unpriced_requests: number | null;
|
|
106
|
+
cost_input: number | null;
|
|
107
|
+
cost_output: number | null;
|
|
108
|
+
cost_cache_read: number | null;
|
|
109
|
+
cost_cache_write: number | null;
|
|
110
|
+
requests: number;
|
|
111
|
+
}
|
|
112
|
+
|
|
92
113
|
let db: Database | null = null;
|
|
93
114
|
|
|
94
115
|
const BACKFILL_COMPLETE = "complete";
|
|
@@ -99,6 +120,12 @@ const PRIORITY_PREMIUM_REQUESTS_BACKFILL_KEY = "premium_requests_priority_v1";
|
|
|
99
120
|
const AGENT_TYPE_BACKFILL_KEY = "agent_type_v1";
|
|
100
121
|
const FORK_DEDUPE_KEY = "fork_dedupe_v1";
|
|
101
122
|
const TOOL_CALLS_BACKFILL_KEY = "tool_calls_v1";
|
|
123
|
+
// Older ingests dropped `Usage.orchestration` (never a stored column) when
|
|
124
|
+
// pricing, so subscription models billed on orchestration tokens — multi-agent
|
|
125
|
+
// Grok most notably — were priced from conversation buckets alone and could not
|
|
126
|
+
// reach the inclusive 200K tier. A one-time full re-parse repairs them through
|
|
127
|
+
// the cost-refreshing UPSERT in `insertMessageStats`.
|
|
128
|
+
const COST_REINGEST_BACKFILL_KEY = "messages_cost_reingest_v1";
|
|
102
129
|
function shouldResetBackfill(value: string | undefined): boolean {
|
|
103
130
|
return value !== BACKFILL_COMPLETE && value !== BACKFILL_PENDING;
|
|
104
131
|
}
|
|
@@ -301,6 +328,7 @@ export async function initDb(): Promise<Database> {
|
|
|
301
328
|
}
|
|
302
329
|
backfillUserMessages(db);
|
|
303
330
|
backfillToolCalls(db);
|
|
331
|
+
backfillReingestCosts(db);
|
|
304
332
|
repairUserMessageLinks(db);
|
|
305
333
|
backfillPriorityPremiumRequests(db);
|
|
306
334
|
backfillAgentType(db);
|
|
@@ -325,10 +353,11 @@ function getCatalogCost(provider: string, modelId: string): ModelCost | null {
|
|
|
325
353
|
return primaryCost;
|
|
326
354
|
}
|
|
327
355
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
356
|
+
const fallbackProvider = provider === "openai-codex" ? "openai" : provider === "xai-oauth" ? "xai" : null;
|
|
357
|
+
if (fallbackProvider) {
|
|
358
|
+
const fallbackCost = getBundledModelCost(fallbackProvider, modelId);
|
|
359
|
+
if (fallbackCost && hasBillableCost(fallbackCost)) {
|
|
360
|
+
return fallbackCost;
|
|
332
361
|
}
|
|
333
362
|
}
|
|
334
363
|
|
|
@@ -339,18 +368,20 @@ function calculateCatalogCost(provider: string, modelId: string, tokens: CostTok
|
|
|
339
368
|
const cost = getCatalogCost(provider, modelId);
|
|
340
369
|
if (!cost) return null;
|
|
341
370
|
|
|
342
|
-
const
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
371
|
+
const orchestration = tokens.orchestration;
|
|
372
|
+
const usage: Usage = {
|
|
373
|
+
...tokens,
|
|
374
|
+
totalTokens:
|
|
375
|
+
tokens.input +
|
|
376
|
+
tokens.output +
|
|
377
|
+
tokens.cacheRead +
|
|
378
|
+
tokens.cacheWrite +
|
|
379
|
+
(orchestration?.input ?? 0) +
|
|
380
|
+
(orchestration?.output ?? 0) +
|
|
381
|
+
(orchestration?.cacheRead ?? 0),
|
|
382
|
+
cost: { ...ZERO_USAGE_COST },
|
|
353
383
|
};
|
|
384
|
+
return calculateUsageCost(cost, usage);
|
|
354
385
|
}
|
|
355
386
|
|
|
356
387
|
function normalizeUsageCost(cost: UsageCost): UsageCost {
|
|
@@ -486,8 +517,9 @@ export function setFileOffset(sessionFile: string, offset: number, lastModified:
|
|
|
486
517
|
* aggregate. The `WHERE NOT EXISTS` clause skips inserts whose
|
|
487
518
|
* `(entry_id, timestamp)` already exists under a different `session_file` —
|
|
488
519
|
* first-write-wins across the lineage. Same-file re-syncs still hit the
|
|
489
|
-
* `ON CONFLICT(session_file, entry_id)` upsert below
|
|
490
|
-
* `premium_requests`
|
|
520
|
+
* `ON CONFLICT(session_file, entry_id)` upsert below, which re-derives the
|
|
521
|
+
* stored cost (orchestration-aware) and keeps `premium_requests` monotonic, so
|
|
522
|
+
* a forced re-parse repairs historical `premium_requests` and cost fix-ups.
|
|
491
523
|
*/
|
|
492
524
|
export function insertMessageStats(stats: MessageStats[]): number {
|
|
493
525
|
if (!db || stats.length === 0) return 0;
|
|
@@ -505,8 +537,13 @@ export function insertMessageStats(stats: MessageStats[]): number {
|
|
|
505
537
|
WHERE entry_id = ? AND timestamp = ? AND session_file <> ?
|
|
506
538
|
)
|
|
507
539
|
ON CONFLICT(session_file, entry_id) DO UPDATE SET
|
|
508
|
-
premium_requests = excluded.premium_requests
|
|
509
|
-
|
|
540
|
+
premium_requests = MAX(messages.premium_requests, excluded.premium_requests),
|
|
541
|
+
cost_input = excluded.cost_input,
|
|
542
|
+
cost_output = excluded.cost_output,
|
|
543
|
+
cost_cache_read = excluded.cost_cache_read,
|
|
544
|
+
cost_cache_write = excluded.cost_cache_write,
|
|
545
|
+
cost_total = excluded.cost_total,
|
|
546
|
+
cost_no_cache_input = excluded.cost_no_cache_input
|
|
510
547
|
`);
|
|
511
548
|
|
|
512
549
|
let inserted = 0;
|
|
@@ -570,6 +607,7 @@ function buildAggregatedStats(rows: AggregatedStatsRow[]): AggregatedStats {
|
|
|
570
607
|
cacheRate: 0,
|
|
571
608
|
cacheSavings: 0,
|
|
572
609
|
totalCost: 0,
|
|
610
|
+
unpricedRequests: 0,
|
|
573
611
|
totalPremiumRequests: 0,
|
|
574
612
|
avgDuration: null,
|
|
575
613
|
avgTtft: null,
|
|
@@ -604,6 +642,7 @@ function buildAggregatedStats(rows: AggregatedStatsRow[]): AggregatedStats {
|
|
|
604
642
|
: 0,
|
|
605
643
|
cacheSavings: noCacheInputCost > 0 ? (noCacheInputCost - cachedPromptCost) / noCacheInputCost : 0,
|
|
606
644
|
totalCost: row.total_cost || 0,
|
|
645
|
+
unpricedRequests: row.unpriced_requests || 0,
|
|
607
646
|
totalPremiumRequests,
|
|
608
647
|
avgDuration: row.avg_duration,
|
|
609
648
|
avgTtft: row.avg_ttft,
|
|
@@ -630,6 +669,7 @@ export function getOverallStats(cutoff?: number): AggregatedStats {
|
|
|
630
669
|
SUM(cache_write_tokens) as total_cache_write_tokens,
|
|
631
670
|
SUM(premium_requests) as total_premium_requests,
|
|
632
671
|
SUM(cost_total) as total_cost,
|
|
672
|
+
SUM(${UNPRICED_XAI_OAUTH_SQL}) as unpriced_requests,
|
|
633
673
|
SUM(CASE WHEN cost_no_cache_input > 0
|
|
634
674
|
THEN cost_input + cost_cache_read + cost_cache_write
|
|
635
675
|
ELSE 0 END) as total_cached_prompt_cost,
|
|
@@ -665,6 +705,7 @@ export function getStatsByModel(cutoff?: number): ModelStats[] {
|
|
|
665
705
|
SUM(cache_write_tokens) as total_cache_write_tokens,
|
|
666
706
|
SUM(premium_requests) as total_premium_requests,
|
|
667
707
|
SUM(cost_total) as total_cost,
|
|
708
|
+
SUM(${UNPRICED_XAI_OAUTH_SQL}) as unpriced_requests,
|
|
668
709
|
SUM(CASE WHEN cost_no_cache_input > 0
|
|
669
710
|
THEN cost_input + cost_cache_read + cost_cache_write
|
|
670
711
|
ELSE 0 END) as total_cached_prompt_cost,
|
|
@@ -706,6 +747,7 @@ export function getStatsByFolder(cutoff?: number): FolderStats[] {
|
|
|
706
747
|
SUM(cache_write_tokens) as total_cache_write_tokens,
|
|
707
748
|
SUM(premium_requests) as total_premium_requests,
|
|
708
749
|
SUM(cost_total) as total_cost,
|
|
750
|
+
SUM(${UNPRICED_XAI_OAUTH_SQL}) as unpriced_requests,
|
|
709
751
|
SUM(CASE WHEN cost_no_cache_input > 0
|
|
710
752
|
THEN cost_input + cost_cache_read + cost_cache_write
|
|
711
753
|
ELSE 0 END) as total_cached_prompt_cost,
|
|
@@ -854,6 +896,7 @@ export function getStatsByProvider(cutoff?: number | null): ProviderAggregate[]
|
|
|
854
896
|
SUM(cache_write_tokens) as total_cache_write_tokens,
|
|
855
897
|
SUM(input_tokens + output_tokens + cache_read_tokens + cache_write_tokens) as total_tokens,
|
|
856
898
|
SUM(cost_total) as total_cost,
|
|
899
|
+
SUM(${UNPRICED_XAI_OAUTH_SQL}) as unpriced_requests,
|
|
857
900
|
SUM(premium_requests) as total_premium_requests,
|
|
858
901
|
AVG(CASE WHEN duration > 0 THEN output_tokens * 1000.0 / duration ELSE NULL END) as avg_tokens_per_second
|
|
859
902
|
FROM messages
|
|
@@ -873,6 +916,7 @@ export function getStatsByProvider(cutoff?: number | null): ProviderAggregate[]
|
|
|
873
916
|
total_cache_write_tokens: number | null;
|
|
874
917
|
total_tokens: number | null;
|
|
875
918
|
total_cost: number | null;
|
|
919
|
+
unpriced_requests: number | null;
|
|
876
920
|
total_premium_requests: number | null;
|
|
877
921
|
avg_tokens_per_second: number | null;
|
|
878
922
|
}>;
|
|
@@ -887,6 +931,7 @@ export function getStatsByProvider(cutoff?: number | null): ProviderAggregate[]
|
|
|
887
931
|
totalCacheWriteTokens: row.total_cache_write_tokens ?? 0,
|
|
888
932
|
totalTokens: row.total_tokens ?? 0,
|
|
889
933
|
totalCost: row.total_cost ?? 0,
|
|
934
|
+
unpricedRequests: row.unpriced_requests ?? 0,
|
|
890
935
|
totalPremiumRequests: row.total_premium_requests ?? 0,
|
|
891
936
|
avgTokensPerSecond: row.avg_tokens_per_second,
|
|
892
937
|
}));
|
|
@@ -949,6 +994,7 @@ export function getProviderTimeSeries(
|
|
|
949
994
|
provider,
|
|
950
995
|
SUM(input_tokens + output_tokens + cache_read_tokens + cache_write_tokens) as total_tokens,
|
|
951
996
|
SUM(cost_total) as cost,
|
|
997
|
+
SUM(${UNPRICED_XAI_OAUTH_SQL}) as unpriced_requests,
|
|
952
998
|
COUNT(*) as requests
|
|
953
999
|
FROM messages
|
|
954
1000
|
${hasCutoff ? "WHERE timestamp >= ?" : ""}
|
|
@@ -962,6 +1008,7 @@ export function getProviderTimeSeries(
|
|
|
962
1008
|
provider: string;
|
|
963
1009
|
total_tokens: number | null;
|
|
964
1010
|
cost: number | null;
|
|
1011
|
+
unpriced_requests: number | null;
|
|
965
1012
|
requests: number;
|
|
966
1013
|
}>;
|
|
967
1014
|
return rows.map(row => ({
|
|
@@ -969,6 +1016,7 @@ export function getProviderTimeSeries(
|
|
|
969
1016
|
provider: row.provider,
|
|
970
1017
|
totalTokens: row.total_tokens ?? 0,
|
|
971
1018
|
cost: row.cost ?? 0,
|
|
1019
|
+
unpricedRequests: row.unpriced_requests ?? 0,
|
|
972
1020
|
requests: row.requests,
|
|
973
1021
|
}));
|
|
974
1022
|
}
|
|
@@ -1118,6 +1166,7 @@ export function getCostTimeSeries(days = 90, cutoff?: number | null): CostTimeSe
|
|
|
1118
1166
|
model,
|
|
1119
1167
|
provider,
|
|
1120
1168
|
SUM(cost_total) as cost,
|
|
1169
|
+
SUM(${UNPRICED_XAI_OAUTH_SQL}) as unpriced_requests,
|
|
1121
1170
|
SUM(cost_input) as cost_input,
|
|
1122
1171
|
SUM(cost_output) as cost_output,
|
|
1123
1172
|
SUM(cost_cache_read) as cost_cache_read,
|
|
@@ -1129,16 +1178,17 @@ export function getCostTimeSeries(days = 90, cutoff?: number | null): CostTimeSe
|
|
|
1129
1178
|
ORDER BY bucket ASC
|
|
1130
1179
|
`);
|
|
1131
1180
|
|
|
1132
|
-
const rows = hasCutoff ?
|
|
1181
|
+
const rows = (hasCutoff ? stmt.all(seriesCutoff) : stmt.all()) as CostTimeSeriesRow[];
|
|
1133
1182
|
return rows.map(row => ({
|
|
1134
1183
|
timestamp: row.bucket,
|
|
1135
1184
|
model: row.model,
|
|
1136
1185
|
provider: row.provider,
|
|
1137
|
-
cost: row.cost,
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1186
|
+
cost: row.cost ?? 0,
|
|
1187
|
+
unpricedRequests: row.unpriced_requests ?? 0,
|
|
1188
|
+
costInput: row.cost_input ?? 0,
|
|
1189
|
+
costOutput: row.cost_output ?? 0,
|
|
1190
|
+
costCacheRead: row.cost_cache_read ?? 0,
|
|
1191
|
+
costCacheWrite: row.cost_cache_write ?? 0,
|
|
1142
1192
|
requests: row.requests,
|
|
1143
1193
|
}));
|
|
1144
1194
|
}
|
|
@@ -1216,6 +1266,29 @@ function backfillToolCalls(database: Database): void {
|
|
|
1216
1266
|
.run(TOOL_CALLS_BACKFILL_KEY, BACKFILL_PENDING);
|
|
1217
1267
|
}
|
|
1218
1268
|
|
|
1269
|
+
/**
|
|
1270
|
+
* One-shot `file_offsets` wipe so the next sync re-parses every session and
|
|
1271
|
+
* re-prices `messages` from source. Pre-fix ingests priced subscription rows
|
|
1272
|
+
* from the four stored token buckets only, dropping `Usage.orchestration`
|
|
1273
|
+
* (never a stored column), so multi-agent Grok and any other orchestration-
|
|
1274
|
+
* billed model were understated and could not reach the inclusive 200K tier.
|
|
1275
|
+
* The re-parse reruns the orchestration-aware pricing in `resolveStoredCost`
|
|
1276
|
+
* and the cost-refreshing UPSERT in {@link insertMessageStats} writes it back.
|
|
1277
|
+
* `messages`/`user_messages`/`tool_calls` re-inserts are idempotent, so the
|
|
1278
|
+
* offset reset is safe. Same sentinel protocol as {@link backfillToolCalls}.
|
|
1279
|
+
*/
|
|
1280
|
+
function backfillReingestCosts(database: Database): void {
|
|
1281
|
+
const row = database.prepare("SELECT value FROM meta WHERE key = ?").get(COST_REINGEST_BACKFILL_KEY) as
|
|
1282
|
+
| { value: string }
|
|
1283
|
+
| undefined;
|
|
1284
|
+
if (!shouldResetBackfill(row?.value)) return;
|
|
1285
|
+
|
|
1286
|
+
database.run("DELETE FROM file_offsets");
|
|
1287
|
+
database
|
|
1288
|
+
.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)")
|
|
1289
|
+
.run(COST_REINGEST_BACKFILL_KEY, BACKFILL_PENDING);
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1219
1292
|
/**
|
|
1220
1293
|
* Reclassify pre-existing `messages` rows by agent type once, after the
|
|
1221
1294
|
* `agent_type` column is added to an older database (every prior row defaulted
|
|
@@ -1341,6 +1414,7 @@ export function markSessionBackfillsComplete(): void {
|
|
|
1341
1414
|
TOOL_CALLS_BACKFILL_KEY,
|
|
1342
1415
|
USER_MESSAGE_LINKS_REPAIR_KEY,
|
|
1343
1416
|
PRIORITY_PREMIUM_REQUESTS_BACKFILL_KEY,
|
|
1417
|
+
COST_REINGEST_BACKFILL_KEY,
|
|
1344
1418
|
]) {
|
|
1345
1419
|
markComplete.run(key, BACKFILL_COMPLETE);
|
|
1346
1420
|
}
|
|
@@ -1700,6 +1774,8 @@ const TOOL_AGGREGATE_COLUMNS = `
|
|
|
1700
1774
|
SUM(COALESCE(m.total_tokens, 0) * 1.0 / t.calls_in_turn) as total_tokens_share,
|
|
1701
1775
|
SUM(COALESCE(m.output_tokens, 0) * 1.0 / t.calls_in_turn) as output_tokens_share,
|
|
1702
1776
|
SUM(COALESCE(m.cost_total, 0) / t.calls_in_turn) as cost_share,
|
|
1777
|
+
SUM(CASE WHEN t.provider = 'xai-oauth' AND COALESCE(m.total_tokens, 0) > 0 AND COALESCE(m.cost_total, 0) = 0
|
|
1778
|
+
THEN 1.0 / t.calls_in_turn ELSE 0 END) as unpriced_requests_share,
|
|
1703
1779
|
MAX(t.timestamp) as last_used
|
|
1704
1780
|
`;
|
|
1705
1781
|
|
|
@@ -1714,6 +1790,7 @@ interface ToolAggregateRow {
|
|
|
1714
1790
|
total_tokens_share: number | null;
|
|
1715
1791
|
output_tokens_share: number | null;
|
|
1716
1792
|
cost_share: number | null;
|
|
1793
|
+
unpriced_requests_share: number | null;
|
|
1717
1794
|
last_used: number;
|
|
1718
1795
|
}
|
|
1719
1796
|
|
|
@@ -1727,6 +1804,7 @@ function rowToToolUsage(row: ToolAggregateRow): ToolUsageStats {
|
|
|
1727
1804
|
totalTokensShare: row.total_tokens_share ?? 0,
|
|
1728
1805
|
outputTokensShare: row.output_tokens_share ?? 0,
|
|
1729
1806
|
costShare: row.cost_share ?? 0,
|
|
1807
|
+
unpricedRequestsShare: row.unpriced_requests_share ?? 0,
|
|
1730
1808
|
lastUsed: row.last_used,
|
|
1731
1809
|
};
|
|
1732
1810
|
}
|
package/src/index.ts
CHANGED
|
@@ -39,10 +39,9 @@ export type {
|
|
|
39
39
|
ToolUsageStats,
|
|
40
40
|
} from "./types";
|
|
41
41
|
|
|
42
|
-
/**
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
function formatCost(n: number): string {
|
|
42
|
+
/** Format an API-equivalent estimate in dollars, or N/A for unpriced usage. */
|
|
43
|
+
function formatCost(n: number, unpricedRequests = 0): string {
|
|
44
|
+
if (n === 0 && unpricedRequests > 0) return "N/A";
|
|
46
45
|
if (n < 0.01) return `$${n.toFixed(4)}`;
|
|
47
46
|
if (n < 1) return `$${n.toFixed(3)}`;
|
|
48
47
|
return `$${n.toFixed(2)}`;
|
|
@@ -69,7 +68,7 @@ async function printStats(): Promise<void> {
|
|
|
69
68
|
console.log(` Output Tokens: ${formatNumber(overall.totalOutputTokens)}`);
|
|
70
69
|
console.log(` Cache Rate: ${formatPercent(overall.cacheRate)}`);
|
|
71
70
|
console.log(` Cache Savings: ${formatPercent(overall.cacheSavings)}`);
|
|
72
|
-
console.log(`
|
|
71
|
+
console.log(` API-equivalent estimate: ${formatCost(overall.totalCost, overall.unpricedRequests)}`);
|
|
73
72
|
console.log(` Premium Requests: ${formatNumber(normalizePremiumRequests(overall.totalPremiumRequests ?? 0))}`);
|
|
74
73
|
console.log(` Avg Duration: ${overall.avgDuration !== null ? formatDuration(overall.avgDuration) : "-"}`);
|
|
75
74
|
console.log(` Avg TTFT: ${overall.avgTtft !== null ? formatDuration(overall.avgTtft) : "-"}`);
|
|
@@ -78,18 +77,20 @@ async function printStats(): Promise<void> {
|
|
|
78
77
|
}
|
|
79
78
|
|
|
80
79
|
if (byModel.length > 0) {
|
|
81
|
-
console.log("\nBy Model:");
|
|
80
|
+
console.log("\nBy Model (API-equivalent estimates):");
|
|
82
81
|
for (const m of byModel.slice(0, 10)) {
|
|
83
82
|
console.log(
|
|
84
|
-
` ${m.model}: ${formatNumber(m.totalRequests)} reqs, ${formatCost(m.totalCost)}, ${formatPercent(m.cacheRate)} cache rate, ${formatPercent(m.cacheSavings)} cache savings`,
|
|
83
|
+
` ${m.model}: ${formatNumber(m.totalRequests)} reqs, ${formatCost(m.totalCost, m.unpricedRequests)}, ${formatPercent(m.cacheRate)} cache rate, ${formatPercent(m.cacheSavings)} cache savings`,
|
|
85
84
|
);
|
|
86
85
|
}
|
|
87
86
|
}
|
|
88
87
|
|
|
89
88
|
if (byFolder.length > 0) {
|
|
90
|
-
console.log("\nBy Folder:");
|
|
89
|
+
console.log("\nBy Folder (API-equivalent estimates):");
|
|
91
90
|
for (const f of byFolder.slice(0, 10)) {
|
|
92
|
-
console.log(
|
|
91
|
+
console.log(
|
|
92
|
+
` ${f.folder}: ${formatNumber(f.totalRequests)} reqs, ${formatCost(f.totalCost, f.unpricedRequests)}`,
|
|
93
|
+
);
|
|
93
94
|
}
|
|
94
95
|
}
|
|
95
96
|
|
package/src/shared-types.ts
CHANGED
|
@@ -34,6 +34,8 @@ export interface AggregatedStats {
|
|
|
34
34
|
cacheSavings: number;
|
|
35
35
|
/** Total cost */
|
|
36
36
|
totalCost: number;
|
|
37
|
+
/** Requests with token usage but no public-equivalent subscription price. */
|
|
38
|
+
unpricedRequests: number;
|
|
37
39
|
/** Total premium requests */
|
|
38
40
|
totalPremiumRequests: number;
|
|
39
41
|
/** Average duration in ms */
|
|
@@ -122,6 +124,8 @@ export interface CostTimeSeriesPoint {
|
|
|
122
124
|
provider: string;
|
|
123
125
|
/** Total cost for this bucket */
|
|
124
126
|
cost: number;
|
|
127
|
+
/** Requests excluded because no public-equivalent subscription price exists. */
|
|
128
|
+
unpricedRequests: number;
|
|
125
129
|
/** Cost breakdown */
|
|
126
130
|
costInput: number;
|
|
127
131
|
costOutput: number;
|
|
@@ -302,6 +306,8 @@ export interface ToolUsageStats {
|
|
|
302
306
|
outputTokensShare: number;
|
|
303
307
|
/** Cost (USD) of invoking turns, attributed per call share. */
|
|
304
308
|
costShare: number;
|
|
309
|
+
/** Share of unpriced subscription requests attributed to this tool. */
|
|
310
|
+
unpricedRequestsShare: number;
|
|
305
311
|
/** Unix ms of the most recent call in range. */
|
|
306
312
|
lastUsed: number;
|
|
307
313
|
}
|
|
@@ -343,6 +349,8 @@ export interface ProviderAggregate {
|
|
|
343
349
|
/** Uncached input + cache reads + cache writes + output. */
|
|
344
350
|
totalTokens: number;
|
|
345
351
|
totalCost: number;
|
|
352
|
+
/** Requests excluded because no public-equivalent subscription price exists. */
|
|
353
|
+
unpricedRequests: number;
|
|
346
354
|
totalPremiumRequests: number;
|
|
347
355
|
avgTokensPerSecond: number | null;
|
|
348
356
|
}
|
|
@@ -366,6 +374,8 @@ export interface ProviderTimeSeriesPoint {
|
|
|
366
374
|
provider: string;
|
|
367
375
|
totalTokens: number;
|
|
368
376
|
cost: number;
|
|
377
|
+
/** Requests excluded because no public-equivalent subscription price exists. */
|
|
378
|
+
unpricedRequests: number;
|
|
369
379
|
requests: number;
|
|
370
380
|
}
|
|
371
381
|
|