@linxiraos/pi-stats 1.0.10 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -284
- package/LICENSE +22 -0
- package/README.md +4 -2
- package/package.json +91 -93
- package/src/aggregator.ts +16 -4
- package/src/client/app/NavRail.tsx +1 -1
- 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 +127 -35
- package/src/embedded-client.generated.txt +0 -1
- package/src/index.ts +10 -9
- package/src/server.ts +3 -2
- package/src/shared-types.ts +14 -1
- package/src/usage-windows.ts +104 -28
- package/dist/client/index.css +0 -1
- package/dist/client/index.html +0 -24
- package/dist/client/index.js +0 -257
- package/dist/client/styles.css +0 -1692
- package/dist/types/aggregator.d.ts +0 -98
- package/dist/types/client/App.d.ts +0 -1
- package/dist/types/client/api.d.ts +0 -22
- package/dist/types/client/app/AppLayout.d.ts +0 -16
- package/dist/types/client/app/NavRail.d.ts +0 -7
- package/dist/types/client/app/RangeControl.d.ts +0 -7
- package/dist/types/client/app/SyncButton.d.ts +0 -14
- package/dist/types/client/app/ThemeToggle.d.ts +0 -1
- package/dist/types/client/app/TopBar.d.ts +0 -15
- package/dist/types/client/app/routes.d.ts +0 -12
- package/dist/types/client/components/AgentTokenShare.d.ts +0 -5
- package/dist/types/client/components/chart-shared.d.ts +0 -173
- package/dist/types/client/components/models-table-shared.d.ts +0 -175
- package/dist/types/client/components/range-meta.d.ts +0 -21
- package/dist/types/client/data/charts.d.ts +0 -1
- package/dist/types/client/data/formatters.d.ts +0 -8
- package/dist/types/client/data/useHashRoute.d.ts +0 -8
- package/dist/types/client/data/useResource.d.ts +0 -13
- package/dist/types/client/data/view-models.d.ts +0 -74
- package/dist/types/client/index.d.ts +0 -2
- package/dist/types/client/routes/BehaviorRoute.d.ts +0 -7
- package/dist/types/client/routes/CostsRoute.d.ts +0 -7
- package/dist/types/client/routes/ErrorsRoute.d.ts +0 -8
- package/dist/types/client/routes/GainRoute.d.ts +0 -7
- package/dist/types/client/routes/ModelsRoute.d.ts +0 -7
- package/dist/types/client/routes/OverviewRoute.d.ts +0 -8
- package/dist/types/client/routes/ProjectsRoute.d.ts +0 -7
- package/dist/types/client/routes/ProvidersRoute.d.ts +0 -7
- package/dist/types/client/routes/RequestsRoute.d.ts +0 -8
- package/dist/types/client/routes/ToolsRoute.d.ts +0 -7
- package/dist/types/client/routes/index.d.ts +0 -10
- package/dist/types/client/types.d.ts +0 -63
- package/dist/types/client/ui/AsyncBoundary.d.ts +0 -12
- package/dist/types/client/ui/DataTable.d.ts +0 -17
- package/dist/types/client/ui/EmptyState.d.ts +0 -7
- package/dist/types/client/ui/ErrorState.d.ts +0 -6
- package/dist/types/client/ui/JsonBlock.d.ts +0 -7
- package/dist/types/client/ui/MetricCluster.d.ts +0 -5
- package/dist/types/client/ui/Panel.d.ts +0 -7
- package/dist/types/client/ui/RequestDrawer.d.ts +0 -5
- package/dist/types/client/ui/SegmentedControl.d.ts +0 -12
- package/dist/types/client/ui/Skeleton.d.ts +0 -8
- package/dist/types/client/ui/StatusPill.d.ts +0 -7
- package/dist/types/client/ui/index.d.ts +0 -11
- package/dist/types/client/useSystemTheme.d.ts +0 -11
- package/dist/types/db.d.ts +0 -159
- package/dist/types/embedded-client.d.ts +0 -18
- package/dist/types/gain-aggregator.d.ts +0 -26
- package/dist/types/index.d.ts +0 -17
- package/dist/types/parser.d.ts +0 -53
- package/dist/types/port-conflict.d.ts +0 -16
- package/dist/types/server.d.ts +0 -14
- package/dist/types/shared-types.d.ts +0 -408
- package/dist/types/sync-worker.d.ts +0 -31
- package/dist/types/types.d.ts +0 -164
- package/dist/types/usage-windows.d.ts +0 -45
- package/dist/types/user-metrics.d.ts +0 -81
package/src/db.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { Database } from "bun:sqlite";
|
|
2
2
|
import * as fs from "node:fs/promises";
|
|
3
3
|
import type { Usage } from "@linxiraos/pi-ai";
|
|
4
|
-
import
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
calculateUncachedInputCost,
|
|
6
|
+
calculateUsageCost,
|
|
7
|
+
type GeneratedProvider,
|
|
8
|
+
getBundledModel,
|
|
9
|
+
} from "@linxiraos/pi-catalog/models";
|
|
10
|
+
import type { ModelCost } from "@linxiraos/pi-catalog/types";
|
|
6
11
|
import { getConfigRootDir, getStatsDbPath } from "@linxiraos/pi-utils";
|
|
12
|
+
|
|
7
13
|
import { classifyAgentType } from "./parser";
|
|
8
14
|
import type {
|
|
9
15
|
AgentType,
|
|
@@ -31,9 +37,8 @@ import type {
|
|
|
31
37
|
UserMessageStats,
|
|
32
38
|
} from "./types";
|
|
33
39
|
|
|
34
|
-
type ModelCost = { input: number; output: number; cacheRead: number; cacheWrite: number };
|
|
35
40
|
type UsageCost = Usage["cost"];
|
|
36
|
-
type CostTokens = Pick<Usage, "input" | "output" | "cacheRead" | "cacheWrite" | "orchestration">;
|
|
41
|
+
type CostTokens = Pick<Usage, "input" | "output" | "cacheRead" | "cacheWrite" | "orchestration" | "cttl">;
|
|
37
42
|
|
|
38
43
|
const ZERO_USAGE_COST: UsageCost = {
|
|
39
44
|
input: 0,
|
|
@@ -43,6 +48,9 @@ const ZERO_USAGE_COST: UsageCost = {
|
|
|
43
48
|
total: 0,
|
|
44
49
|
};
|
|
45
50
|
|
|
51
|
+
const UNPRICED_XAI_OAUTH_SQL =
|
|
52
|
+
"CASE WHEN provider = 'xai-oauth' AND total_tokens > 0 AND cost_total = 0 THEN 1 ELSE 0 END";
|
|
53
|
+
|
|
46
54
|
interface CostBackfillRow {
|
|
47
55
|
id: number;
|
|
48
56
|
provider: string;
|
|
@@ -71,6 +79,7 @@ interface AggregatedStatsRow {
|
|
|
71
79
|
total_cache_write_tokens: number | null;
|
|
72
80
|
total_premium_requests: number | null;
|
|
73
81
|
total_cost: number | null;
|
|
82
|
+
unpriced_requests: number | null;
|
|
74
83
|
total_cached_prompt_cost: number | null;
|
|
75
84
|
total_no_cache_input_cost: number | null;
|
|
76
85
|
avg_duration: number | null;
|
|
@@ -89,6 +98,19 @@ interface FolderStatsRow extends AggregatedStatsRow {
|
|
|
89
98
|
folder: string;
|
|
90
99
|
}
|
|
91
100
|
|
|
101
|
+
interface CostTimeSeriesRow {
|
|
102
|
+
bucket: number;
|
|
103
|
+
model: string;
|
|
104
|
+
provider: string;
|
|
105
|
+
cost: number | null;
|
|
106
|
+
unpriced_requests: number | null;
|
|
107
|
+
cost_input: number | null;
|
|
108
|
+
cost_output: number | null;
|
|
109
|
+
cost_cache_read: number | null;
|
|
110
|
+
cost_cache_write: number | null;
|
|
111
|
+
requests: number;
|
|
112
|
+
}
|
|
113
|
+
|
|
92
114
|
let db: Database | null = null;
|
|
93
115
|
|
|
94
116
|
const BACKFILL_COMPLETE = "complete";
|
|
@@ -99,6 +121,12 @@ const PRIORITY_PREMIUM_REQUESTS_BACKFILL_KEY = "premium_requests_priority_v1";
|
|
|
99
121
|
const AGENT_TYPE_BACKFILL_KEY = "agent_type_v1";
|
|
100
122
|
const FORK_DEDUPE_KEY = "fork_dedupe_v1";
|
|
101
123
|
const TOOL_CALLS_BACKFILL_KEY = "tool_calls_v1";
|
|
124
|
+
// Older ingests dropped `Usage.orchestration` (never a stored column) when
|
|
125
|
+
// pricing, so subscription models billed on orchestration tokens — multi-agent
|
|
126
|
+
// Grok most notably — were priced from conversation buckets alone and could not
|
|
127
|
+
// reach the inclusive 200K tier. A one-time full re-parse repairs them through
|
|
128
|
+
// the cost-refreshing UPSERT in `insertMessageStats`.
|
|
129
|
+
const COST_REINGEST_BACKFILL_KEY = "messages_cost_reingest_v1";
|
|
102
130
|
function shouldResetBackfill(value: string | undefined): boolean {
|
|
103
131
|
return value !== BACKFILL_COMPLETE && value !== BACKFILL_PENDING;
|
|
104
132
|
}
|
|
@@ -301,6 +329,7 @@ export async function initDb(): Promise<Database> {
|
|
|
301
329
|
}
|
|
302
330
|
backfillUserMessages(db);
|
|
303
331
|
backfillToolCalls(db);
|
|
332
|
+
backfillReingestCosts(db);
|
|
304
333
|
repairUserMessageLinks(db);
|
|
305
334
|
backfillPriorityPremiumRequests(db);
|
|
306
335
|
backfillAgentType(db);
|
|
@@ -325,10 +354,11 @@ function getCatalogCost(provider: string, modelId: string): ModelCost | null {
|
|
|
325
354
|
return primaryCost;
|
|
326
355
|
}
|
|
327
356
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
357
|
+
const fallbackProvider = provider === "openai-codex" ? "openai" : provider === "xai-oauth" ? "xai" : null;
|
|
358
|
+
if (fallbackProvider) {
|
|
359
|
+
const fallbackCost = getBundledModelCost(fallbackProvider, modelId);
|
|
360
|
+
if (fallbackCost && hasBillableCost(fallbackCost)) {
|
|
361
|
+
return fallbackCost;
|
|
332
362
|
}
|
|
333
363
|
}
|
|
334
364
|
|
|
@@ -339,27 +369,42 @@ function calculateCatalogCost(provider: string, modelId: string, tokens: CostTok
|
|
|
339
369
|
const cost = getCatalogCost(provider, modelId);
|
|
340
370
|
if (!cost) return null;
|
|
341
371
|
|
|
342
|
-
const
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
372
|
+
const orchestration = tokens.orchestration;
|
|
373
|
+
const usage: Usage = {
|
|
374
|
+
...tokens,
|
|
375
|
+
totalTokens:
|
|
376
|
+
tokens.input +
|
|
377
|
+
tokens.output +
|
|
378
|
+
tokens.cacheRead +
|
|
379
|
+
tokens.cacheWrite +
|
|
380
|
+
(orchestration?.input ?? 0) +
|
|
381
|
+
(orchestration?.output ?? 0) +
|
|
382
|
+
(orchestration?.cacheRead ?? 0),
|
|
383
|
+
cost: { ...ZERO_USAGE_COST },
|
|
353
384
|
};
|
|
385
|
+
return calculateUsageCost(cost, usage);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function normalizeUsageCost(cost: UsageCost): UsageCost {
|
|
389
|
+
const input = cost.input ?? 0;
|
|
390
|
+
const output = cost.output ?? 0;
|
|
391
|
+
const cacheRead = cost.cacheRead ?? 0;
|
|
392
|
+
const cacheWrite = cost.cacheWrite ?? 0;
|
|
393
|
+
const total = cost.total ?? input + output + cacheRead + cacheWrite;
|
|
394
|
+
return { input, output, cacheRead, cacheWrite, total };
|
|
354
395
|
}
|
|
355
396
|
|
|
356
397
|
function resolveStoredCost(stats: MessageStats): UsageCost {
|
|
357
|
-
// `usage.cost` was optional in older session files
|
|
358
|
-
//
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
398
|
+
// `usage.cost` was optional in older session files, and legacy payloads
|
|
399
|
+
// can carry a partially-populated cost object (e.g. only `total`). The
|
|
400
|
+
// messages table declares every cost_* column as REAL NOT NULL, so any
|
|
401
|
+
// missing field must be normalised here before binding into SQLite.
|
|
402
|
+
const raw: UsageCost | undefined = stats.usage.cost;
|
|
403
|
+
const storedCost = raw ? normalizeUsageCost(raw) : undefined;
|
|
404
|
+
|
|
405
|
+
// A missing total is derived from the stored components. An explicit zero
|
|
406
|
+
// remains the sentinel for catalog-based correction.
|
|
407
|
+
if (storedCost && (raw?.total ?? storedCost.total) !== 0) return storedCost;
|
|
363
408
|
|
|
364
409
|
return calculateCatalogCost(stats.provider, stats.model, stats.usage) ?? storedCost ?? ZERO_USAGE_COST;
|
|
365
410
|
}
|
|
@@ -473,8 +518,9 @@ export function setFileOffset(sessionFile: string, offset: number, lastModified:
|
|
|
473
518
|
* aggregate. The `WHERE NOT EXISTS` clause skips inserts whose
|
|
474
519
|
* `(entry_id, timestamp)` already exists under a different `session_file` —
|
|
475
520
|
* first-write-wins across the lineage. Same-file re-syncs still hit the
|
|
476
|
-
* `ON CONFLICT(session_file, entry_id)` upsert below
|
|
477
|
-
* `premium_requests`
|
|
521
|
+
* `ON CONFLICT(session_file, entry_id)` upsert below, which re-derives the
|
|
522
|
+
* stored cost (orchestration-aware) and keeps `premium_requests` monotonic, so
|
|
523
|
+
* a forced re-parse repairs historical `premium_requests` and cost fix-ups.
|
|
478
524
|
*/
|
|
479
525
|
export function insertMessageStats(stats: MessageStats[]): number {
|
|
480
526
|
if (!db || stats.length === 0) return 0;
|
|
@@ -492,8 +538,13 @@ export function insertMessageStats(stats: MessageStats[]): number {
|
|
|
492
538
|
WHERE entry_id = ? AND timestamp = ? AND session_file <> ?
|
|
493
539
|
)
|
|
494
540
|
ON CONFLICT(session_file, entry_id) DO UPDATE SET
|
|
495
|
-
premium_requests = excluded.premium_requests
|
|
496
|
-
|
|
541
|
+
premium_requests = MAX(messages.premium_requests, excluded.premium_requests),
|
|
542
|
+
cost_input = excluded.cost_input,
|
|
543
|
+
cost_output = excluded.cost_output,
|
|
544
|
+
cost_cache_read = excluded.cost_cache_read,
|
|
545
|
+
cost_cache_write = excluded.cost_cache_write,
|
|
546
|
+
cost_total = excluded.cost_total,
|
|
547
|
+
cost_no_cache_input = excluded.cost_no_cache_input
|
|
497
548
|
`);
|
|
498
549
|
|
|
499
550
|
let inserted = 0;
|
|
@@ -557,6 +608,7 @@ function buildAggregatedStats(rows: AggregatedStatsRow[]): AggregatedStats {
|
|
|
557
608
|
cacheRate: 0,
|
|
558
609
|
cacheSavings: 0,
|
|
559
610
|
totalCost: 0,
|
|
611
|
+
unpricedRequests: 0,
|
|
560
612
|
totalPremiumRequests: 0,
|
|
561
613
|
avgDuration: null,
|
|
562
614
|
avgTtft: null,
|
|
@@ -591,6 +643,7 @@ function buildAggregatedStats(rows: AggregatedStatsRow[]): AggregatedStats {
|
|
|
591
643
|
: 0,
|
|
592
644
|
cacheSavings: noCacheInputCost > 0 ? (noCacheInputCost - cachedPromptCost) / noCacheInputCost : 0,
|
|
593
645
|
totalCost: row.total_cost || 0,
|
|
646
|
+
unpricedRequests: row.unpriced_requests || 0,
|
|
594
647
|
totalPremiumRequests,
|
|
595
648
|
avgDuration: row.avg_duration,
|
|
596
649
|
avgTtft: row.avg_ttft,
|
|
@@ -617,6 +670,7 @@ export function getOverallStats(cutoff?: number): AggregatedStats {
|
|
|
617
670
|
SUM(cache_write_tokens) as total_cache_write_tokens,
|
|
618
671
|
SUM(premium_requests) as total_premium_requests,
|
|
619
672
|
SUM(cost_total) as total_cost,
|
|
673
|
+
SUM(${UNPRICED_XAI_OAUTH_SQL}) as unpriced_requests,
|
|
620
674
|
SUM(CASE WHEN cost_no_cache_input > 0
|
|
621
675
|
THEN cost_input + cost_cache_read + cost_cache_write
|
|
622
676
|
ELSE 0 END) as total_cached_prompt_cost,
|
|
@@ -652,6 +706,7 @@ export function getStatsByModel(cutoff?: number): ModelStats[] {
|
|
|
652
706
|
SUM(cache_write_tokens) as total_cache_write_tokens,
|
|
653
707
|
SUM(premium_requests) as total_premium_requests,
|
|
654
708
|
SUM(cost_total) as total_cost,
|
|
709
|
+
SUM(${UNPRICED_XAI_OAUTH_SQL}) as unpriced_requests,
|
|
655
710
|
SUM(CASE WHEN cost_no_cache_input > 0
|
|
656
711
|
THEN cost_input + cost_cache_read + cost_cache_write
|
|
657
712
|
ELSE 0 END) as total_cached_prompt_cost,
|
|
@@ -693,6 +748,7 @@ export function getStatsByFolder(cutoff?: number): FolderStats[] {
|
|
|
693
748
|
SUM(cache_write_tokens) as total_cache_write_tokens,
|
|
694
749
|
SUM(premium_requests) as total_premium_requests,
|
|
695
750
|
SUM(cost_total) as total_cost,
|
|
751
|
+
SUM(${UNPRICED_XAI_OAUTH_SQL}) as unpriced_requests,
|
|
696
752
|
SUM(CASE WHEN cost_no_cache_input > 0
|
|
697
753
|
THEN cost_input + cost_cache_read + cost_cache_write
|
|
698
754
|
ELSE 0 END) as total_cached_prompt_cost,
|
|
@@ -841,6 +897,7 @@ export function getStatsByProvider(cutoff?: number | null): ProviderAggregate[]
|
|
|
841
897
|
SUM(cache_write_tokens) as total_cache_write_tokens,
|
|
842
898
|
SUM(input_tokens + output_tokens + cache_read_tokens + cache_write_tokens) as total_tokens,
|
|
843
899
|
SUM(cost_total) as total_cost,
|
|
900
|
+
SUM(${UNPRICED_XAI_OAUTH_SQL}) as unpriced_requests,
|
|
844
901
|
SUM(premium_requests) as total_premium_requests,
|
|
845
902
|
AVG(CASE WHEN duration > 0 THEN output_tokens * 1000.0 / duration ELSE NULL END) as avg_tokens_per_second
|
|
846
903
|
FROM messages
|
|
@@ -860,6 +917,7 @@ export function getStatsByProvider(cutoff?: number | null): ProviderAggregate[]
|
|
|
860
917
|
total_cache_write_tokens: number | null;
|
|
861
918
|
total_tokens: number | null;
|
|
862
919
|
total_cost: number | null;
|
|
920
|
+
unpriced_requests: number | null;
|
|
863
921
|
total_premium_requests: number | null;
|
|
864
922
|
avg_tokens_per_second: number | null;
|
|
865
923
|
}>;
|
|
@@ -874,6 +932,7 @@ export function getStatsByProvider(cutoff?: number | null): ProviderAggregate[]
|
|
|
874
932
|
totalCacheWriteTokens: row.total_cache_write_tokens ?? 0,
|
|
875
933
|
totalTokens: row.total_tokens ?? 0,
|
|
876
934
|
totalCost: row.total_cost ?? 0,
|
|
935
|
+
unpricedRequests: row.unpriced_requests ?? 0,
|
|
877
936
|
totalPremiumRequests: row.total_premium_requests ?? 0,
|
|
878
937
|
avgTokensPerSecond: row.avg_tokens_per_second,
|
|
879
938
|
}));
|
|
@@ -936,6 +995,7 @@ export function getProviderTimeSeries(
|
|
|
936
995
|
provider,
|
|
937
996
|
SUM(input_tokens + output_tokens + cache_read_tokens + cache_write_tokens) as total_tokens,
|
|
938
997
|
SUM(cost_total) as cost,
|
|
998
|
+
SUM(${UNPRICED_XAI_OAUTH_SQL}) as unpriced_requests,
|
|
939
999
|
COUNT(*) as requests
|
|
940
1000
|
FROM messages
|
|
941
1001
|
${hasCutoff ? "WHERE timestamp >= ?" : ""}
|
|
@@ -949,6 +1009,7 @@ export function getProviderTimeSeries(
|
|
|
949
1009
|
provider: string;
|
|
950
1010
|
total_tokens: number | null;
|
|
951
1011
|
cost: number | null;
|
|
1012
|
+
unpriced_requests: number | null;
|
|
952
1013
|
requests: number;
|
|
953
1014
|
}>;
|
|
954
1015
|
return rows.map(row => ({
|
|
@@ -956,6 +1017,7 @@ export function getProviderTimeSeries(
|
|
|
956
1017
|
provider: row.provider,
|
|
957
1018
|
totalTokens: row.total_tokens ?? 0,
|
|
958
1019
|
cost: row.cost ?? 0,
|
|
1020
|
+
unpricedRequests: row.unpriced_requests ?? 0,
|
|
959
1021
|
requests: row.requests,
|
|
960
1022
|
}));
|
|
961
1023
|
}
|
|
@@ -1105,6 +1167,7 @@ export function getCostTimeSeries(days = 90, cutoff?: number | null): CostTimeSe
|
|
|
1105
1167
|
model,
|
|
1106
1168
|
provider,
|
|
1107
1169
|
SUM(cost_total) as cost,
|
|
1170
|
+
SUM(${UNPRICED_XAI_OAUTH_SQL}) as unpriced_requests,
|
|
1108
1171
|
SUM(cost_input) as cost_input,
|
|
1109
1172
|
SUM(cost_output) as cost_output,
|
|
1110
1173
|
SUM(cost_cache_read) as cost_cache_read,
|
|
@@ -1116,16 +1179,17 @@ export function getCostTimeSeries(days = 90, cutoff?: number | null): CostTimeSe
|
|
|
1116
1179
|
ORDER BY bucket ASC
|
|
1117
1180
|
`);
|
|
1118
1181
|
|
|
1119
|
-
const rows = hasCutoff ?
|
|
1182
|
+
const rows = (hasCutoff ? stmt.all(seriesCutoff) : stmt.all()) as CostTimeSeriesRow[];
|
|
1120
1183
|
return rows.map(row => ({
|
|
1121
1184
|
timestamp: row.bucket,
|
|
1122
1185
|
model: row.model,
|
|
1123
1186
|
provider: row.provider,
|
|
1124
|
-
cost: row.cost,
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1187
|
+
cost: row.cost ?? 0,
|
|
1188
|
+
unpricedRequests: row.unpriced_requests ?? 0,
|
|
1189
|
+
costInput: row.cost_input ?? 0,
|
|
1190
|
+
costOutput: row.cost_output ?? 0,
|
|
1191
|
+
costCacheRead: row.cost_cache_read ?? 0,
|
|
1192
|
+
costCacheWrite: row.cost_cache_write ?? 0,
|
|
1129
1193
|
requests: row.requests,
|
|
1130
1194
|
}));
|
|
1131
1195
|
}
|
|
@@ -1203,6 +1267,29 @@ function backfillToolCalls(database: Database): void {
|
|
|
1203
1267
|
.run(TOOL_CALLS_BACKFILL_KEY, BACKFILL_PENDING);
|
|
1204
1268
|
}
|
|
1205
1269
|
|
|
1270
|
+
/**
|
|
1271
|
+
* One-shot `file_offsets` wipe so the next sync re-parses every session and
|
|
1272
|
+
* re-prices `messages` from source. Pre-fix ingests priced subscription rows
|
|
1273
|
+
* from the four stored token buckets only, dropping `Usage.orchestration`
|
|
1274
|
+
* (never a stored column), so multi-agent Grok and any other orchestration-
|
|
1275
|
+
* billed model were understated and could not reach the inclusive 200K tier.
|
|
1276
|
+
* The re-parse reruns the orchestration-aware pricing in `resolveStoredCost`
|
|
1277
|
+
* and the cost-refreshing UPSERT in {@link insertMessageStats} writes it back.
|
|
1278
|
+
* `messages`/`user_messages`/`tool_calls` re-inserts are idempotent, so the
|
|
1279
|
+
* offset reset is safe. Same sentinel protocol as {@link backfillToolCalls}.
|
|
1280
|
+
*/
|
|
1281
|
+
function backfillReingestCosts(database: Database): void {
|
|
1282
|
+
const row = database.prepare("SELECT value FROM meta WHERE key = ?").get(COST_REINGEST_BACKFILL_KEY) as
|
|
1283
|
+
| { value: string }
|
|
1284
|
+
| undefined;
|
|
1285
|
+
if (!shouldResetBackfill(row?.value)) return;
|
|
1286
|
+
|
|
1287
|
+
database.run("DELETE FROM file_offsets");
|
|
1288
|
+
database
|
|
1289
|
+
.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)")
|
|
1290
|
+
.run(COST_REINGEST_BACKFILL_KEY, BACKFILL_PENDING);
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1206
1293
|
/**
|
|
1207
1294
|
* Reclassify pre-existing `messages` rows by agent type once, after the
|
|
1208
1295
|
* `agent_type` column is added to an older database (every prior row defaulted
|
|
@@ -1328,6 +1415,7 @@ export function markSessionBackfillsComplete(): void {
|
|
|
1328
1415
|
TOOL_CALLS_BACKFILL_KEY,
|
|
1329
1416
|
USER_MESSAGE_LINKS_REPAIR_KEY,
|
|
1330
1417
|
PRIORITY_PREMIUM_REQUESTS_BACKFILL_KEY,
|
|
1418
|
+
COST_REINGEST_BACKFILL_KEY,
|
|
1331
1419
|
]) {
|
|
1332
1420
|
markComplete.run(key, BACKFILL_COMPLETE);
|
|
1333
1421
|
}
|
|
@@ -1687,6 +1775,8 @@ const TOOL_AGGREGATE_COLUMNS = `
|
|
|
1687
1775
|
SUM(COALESCE(m.total_tokens, 0) * 1.0 / t.calls_in_turn) as total_tokens_share,
|
|
1688
1776
|
SUM(COALESCE(m.output_tokens, 0) * 1.0 / t.calls_in_turn) as output_tokens_share,
|
|
1689
1777
|
SUM(COALESCE(m.cost_total, 0) / t.calls_in_turn) as cost_share,
|
|
1778
|
+
SUM(CASE WHEN t.provider = 'xai-oauth' AND COALESCE(m.total_tokens, 0) > 0 AND COALESCE(m.cost_total, 0) = 0
|
|
1779
|
+
THEN 1.0 / t.calls_in_turn ELSE 0 END) as unpriced_requests_share,
|
|
1690
1780
|
MAX(t.timestamp) as last_used
|
|
1691
1781
|
`;
|
|
1692
1782
|
|
|
@@ -1701,6 +1791,7 @@ interface ToolAggregateRow {
|
|
|
1701
1791
|
total_tokens_share: number | null;
|
|
1702
1792
|
output_tokens_share: number | null;
|
|
1703
1793
|
cost_share: number | null;
|
|
1794
|
+
unpriced_requests_share: number | null;
|
|
1704
1795
|
last_used: number;
|
|
1705
1796
|
}
|
|
1706
1797
|
|
|
@@ -1714,6 +1805,7 @@ function rowToToolUsage(row: ToolAggregateRow): ToolUsageStats {
|
|
|
1714
1805
|
totalTokensShare: row.total_tokens_share ?? 0,
|
|
1715
1806
|
outputTokensShare: row.output_tokens_share ?? 0,
|
|
1716
1807
|
costShare: row.cost_share ?? 0,
|
|
1808
|
+
unpricedRequestsShare: row.unpriced_requests_share ?? 0,
|
|
1717
1809
|
lastUsed: row.last_used,
|
|
1718
1810
|
};
|
|
1719
1811
|
}
|