@tokentop/ttop 0.2.2 → 0.4.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/README.md +2 -5
- package/package.json +8 -5
- package/src/agents/aggregator.ts +39 -19
- package/src/agents/costing.ts +11 -9
- package/src/agents/index.ts +7 -9
- package/src/agents/types.ts +2 -2
- package/src/cli.ts +28 -23
- package/src/config/schema.test.ts +42 -40
- package/src/config/schema.ts +26 -26
- package/src/demo/simulator.ts +161 -122
- package/src/plugins/agents/index.ts +2 -2
- package/src/plugins/auth-sources.ts +42 -38
- package/src/plugins/lifecycle.ts +42 -38
- package/src/plugins/loader.test.ts +49 -49
- package/src/plugins/loader.ts +67 -63
- package/src/plugins/notification-bus.ts +21 -16
- package/src/plugins/notifications/index.ts +2 -2
- package/src/plugins/notifications/terminal-bell.ts +36 -23
- package/src/plugins/notifications/visual-flash.ts +31 -19
- package/src/plugins/npm-installer.ts +23 -25
- package/src/plugins/plugin-context-factory.ts +10 -10
- package/src/plugins/plugin-host.ts +12 -5
- package/src/plugins/providers/anthropic.ts +64 -57
- package/src/plugins/providers/antigravity.ts +81 -69
- package/src/plugins/providers/codex.ts +42 -42
- package/src/plugins/providers/gemini.ts +106 -87
- package/src/plugins/providers/github-copilot.ts +49 -45
- package/src/plugins/providers/index.ts +10 -12
- package/src/plugins/providers/minimax.ts +50 -44
- package/src/plugins/providers/openai-api.ts +82 -62
- package/src/plugins/providers/opencode-zen.ts +46 -39
- package/src/plugins/providers/perplexity.ts +55 -46
- package/src/plugins/providers/zai.ts +68 -57
- package/src/plugins/registry.ts +80 -71
- package/src/plugins/sandbox-guard.test.ts +45 -48
- package/src/plugins/sandbox-guard.ts +20 -25
- package/src/plugins/sandbox.ts +26 -48
- package/src/plugins/themes/catppuccin-latte.ts +29 -29
- package/src/plugins/themes/catppuccin-mocha.ts +29 -29
- package/src/plugins/themes/claude-code.ts +29 -29
- package/src/plugins/themes/dracula.ts +29 -29
- package/src/plugins/themes/github-light.ts +29 -29
- package/src/plugins/themes/gruvbox-dark.ts +29 -29
- package/src/plugins/themes/gruvbox-light.ts +29 -29
- package/src/plugins/themes/index.ts +15 -15
- package/src/plugins/themes/kanagawa.ts +29 -29
- package/src/plugins/themes/nord.ts +29 -29
- package/src/plugins/themes/one-dark.ts +29 -29
- package/src/plugins/themes/opencode.ts +31 -31
- package/src/plugins/themes/rose-pine-dawn.ts +29 -29
- package/src/plugins/themes/rose-pine.ts +29 -29
- package/src/plugins/themes/solarized-light.ts +29 -29
- package/src/plugins/themes/tokyo-night.ts +29 -29
- package/src/plugins/types/agent.ts +5 -5
- package/src/plugins/types/base.ts +35 -24
- package/src/plugins/types/index.ts +9 -9
- package/src/plugins/types/notification.ts +12 -12
- package/src/plugins/types/provider.ts +12 -8
- package/src/plugins/types/theme.ts +31 -23
- package/src/plugins/update-checker.ts +6 -6
- package/src/pricing/estimator.test.ts +39 -44
- package/src/pricing/estimator.ts +7 -13
- package/src/pricing/fallback.test.ts +30 -34
- package/src/pricing/fallback.ts +54 -27
- package/src/pricing/index.ts +18 -12
- package/src/pricing/models-dev.ts +6 -6
- package/src/storage/database.ts +56 -46
- package/src/storage/db.ts +12 -12
- package/src/storage/index.ts +44 -45
- package/src/storage/migrations/index.ts +2 -2
- package/src/storage/paths.ts +13 -13
- package/src/storage/persistence-service.ts +206 -0
- package/src/storage/repos/agentSessions.ts +33 -23
- package/src/storage/repos/providerSnapshots.ts +5 -5
- package/src/storage/repos/usageEvents.ts +40 -28
- package/src/storage/retention.ts +146 -0
- package/src/storage/types.ts +5 -5
- package/src/tui/App.tsx +180 -137
- package/src/tui/components/CommandPalette.tsx +46 -34
- package/src/tui/components/DebugConsole.tsx +39 -33
- package/src/tui/components/DebugPanel.tsx +314 -183
- package/src/tui/components/FooterHints.tsx +2 -2
- package/src/tui/components/GhostProviderCard.tsx +37 -35
- package/src/tui/components/Header.tsx +68 -35
- package/src/tui/components/HelpOverlay.tsx +99 -29
- package/src/tui/components/InlineGauge.tsx +10 -7
- package/src/tui/components/InlineSparkline.tsx +17 -11
- package/src/tui/components/KpiStrip.tsx +128 -98
- package/src/tui/components/LimitGauge.tsx +96 -59
- package/src/tui/components/ModalBackdrop.tsx +4 -4
- package/src/tui/components/ProviderAggregateStrip.tsx +69 -38
- package/src/tui/components/ProviderCard.tsx +232 -226
- package/src/tui/components/ProviderDetailPanel.tsx +133 -58
- package/src/tui/components/ProviderLimitsPanel.tsx +126 -89
- package/src/tui/components/ProvidersList.tsx +215 -126
- package/src/tui/components/SessionDetailsDrawer.tsx +191 -94
- package/src/tui/components/SessionsTable.tsx +320 -169
- package/src/tui/components/SettingsModal.tsx +593 -269
- package/src/tui/components/SidebarBreakdown.tsx +30 -16
- package/src/tui/components/Skeleton.tsx +24 -17
- package/src/tui/components/SmartSidebar.tsx +360 -258
- package/src/tui/components/Sparkline.tsx +64 -52
- package/src/tui/components/Spinner.tsx +3 -3
- package/src/tui/components/StatusBar.tsx +27 -27
- package/src/tui/components/ThemePicker.tsx +67 -66
- package/src/tui/components/Toast.tsx +9 -6
- package/src/tui/components/UsageGauge.tsx +10 -13
- package/src/tui/components/index.ts +5 -5
- package/src/tui/contexts/AgentSessionContext.tsx +164 -151
- package/src/tui/contexts/ConfigContext.tsx +15 -13
- package/src/tui/contexts/DashboardRuntimeContext.tsx +26 -26
- package/src/tui/contexts/DemoModeContext.tsx +22 -15
- package/src/tui/contexts/DrawerContext.tsx +11 -9
- package/src/tui/contexts/InputContext.tsx +2 -2
- package/src/tui/contexts/LogContext.tsx +62 -47
- package/src/tui/contexts/PluginContext.tsx +238 -183
- package/src/tui/contexts/RealTimeActivityContext.tsx +70 -36
- package/src/tui/contexts/StorageContext.tsx +95 -149
- package/src/tui/contexts/ThemeContext.tsx +21 -16
- package/src/tui/contexts/TimeWindowContext.tsx +72 -61
- package/src/tui/contexts/ToastContext.tsx +10 -7
- package/src/tui/contexts/index.ts +9 -5
- package/src/tui/createApp.tsx +8 -12
- package/src/tui/debug/captureFrame.ts +25 -28
- package/src/tui/debug/snapshot.tsx +179 -162
- package/src/tui/driver/assertions.ts +30 -22
- package/src/tui/driver/cli.ts +231 -221
- package/src/tui/driver/coverage.ts +27 -44
- package/src/tui/driver/demo-snapshot.ts +33 -33
- package/src/tui/driver/diff.ts +26 -32
- package/src/tui/driver/driver.ts +43 -48
- package/src/tui/driver/index.ts +36 -40
- package/src/tui/driver/recorder.ts +37 -48
- package/src/tui/driver/test-workflow.ts +51 -51
- package/src/tui/driver/test.ts +25 -25
- package/src/tui/hooks/index.ts +24 -20
- package/src/tui/hooks/useAnimatedValue.ts +13 -18
- package/src/tui/hooks/useDashboardKeyboard.ts +118 -98
- package/src/tui/hooks/useDashboardState.ts +26 -24
- package/src/tui/hooks/useEmaActivity.ts +82 -74
- package/src/tui/hooks/useEntranceAnimation.ts +11 -11
- package/src/tui/hooks/useExitAnimation.ts +7 -3
- package/src/tui/hooks/usePulse.ts +4 -4
- package/src/tui/hooks/useSafeRenderer.ts +5 -5
- package/src/tui/hooks/useSessionEnrichedProviders.ts +150 -0
- package/src/tui/hooks/useValueFlash.ts +10 -14
- package/src/tui/index.tsx +29 -6
- package/src/tui/shutdown.ts +19 -0
- package/src/tui/utils/providerColor.ts +1 -1
- package/src/tui/views/Dashboard.tsx +164 -102
- package/src/tui/views/HistoricalTrendsView.tsx +579 -250
- package/src/tui/views/ProjectsView.tsx +495 -243
- package/src/tui/views/RealTimeDashboard.tsx +176 -116
- package/src/tui/views/SettingsView.tsx +225 -189
- package/src/tui/views/index.ts +2 -2
- package/src/types/opentui-test-utils.d.ts +7 -7
- package/src/utils/clipboard.ts +29 -29
- package/src/utils/currency.test.ts +53 -53
- package/src/utils/currency.ts +35 -32
- package/src/version.ts +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { getDatabase } from
|
|
2
|
-
import type {
|
|
1
|
+
import { getDatabase } from "../db.ts";
|
|
2
|
+
import type { TimeSeriesFilters, TimeSeriesPoint, UsageEventInsert } from "../types.ts";
|
|
3
3
|
|
|
4
4
|
const INSERT_SQL = `
|
|
5
5
|
INSERT INTO usage_events (
|
|
@@ -27,7 +27,7 @@ export function insertUsageEvent(event: UsageEventInsert): number {
|
|
|
27
27
|
event.cacheWriteTokens,
|
|
28
28
|
event.costUsd,
|
|
29
29
|
event.requestCount,
|
|
30
|
-
event.pricingSource ?? null
|
|
30
|
+
event.pricingSource ?? null,
|
|
31
31
|
);
|
|
32
32
|
|
|
33
33
|
return Number(result.lastInsertRowid);
|
|
@@ -55,7 +55,7 @@ export function insertUsageEventBatch(events: UsageEventInsert[]): void {
|
|
|
55
55
|
e.cacheWriteTokens,
|
|
56
56
|
e.costUsd,
|
|
57
57
|
e.requestCount,
|
|
58
|
-
e.pricingSource ?? null
|
|
58
|
+
e.pricingSource ?? null,
|
|
59
59
|
);
|
|
60
60
|
}
|
|
61
61
|
});
|
|
@@ -67,31 +67,31 @@ export function queryUsageTimeSeries(
|
|
|
67
67
|
startMs: number,
|
|
68
68
|
endMs: number,
|
|
69
69
|
bucketMs: number,
|
|
70
|
-
filters: TimeSeriesFilters = {}
|
|
70
|
+
filters: TimeSeriesFilters = {},
|
|
71
71
|
): TimeSeriesPoint[] {
|
|
72
72
|
const db = getDatabase();
|
|
73
73
|
|
|
74
|
-
const conditions: string[] = [
|
|
74
|
+
const conditions: string[] = ["timestamp >= ?", "timestamp <= ?"];
|
|
75
75
|
const params: (string | number)[] = [startMs, endMs];
|
|
76
76
|
|
|
77
77
|
if (filters.provider) {
|
|
78
|
-
conditions.push(
|
|
78
|
+
conditions.push("provider = ?");
|
|
79
79
|
params.push(filters.provider);
|
|
80
80
|
}
|
|
81
81
|
if (filters.model) {
|
|
82
|
-
conditions.push(
|
|
82
|
+
conditions.push("model = ?");
|
|
83
83
|
params.push(filters.model);
|
|
84
84
|
}
|
|
85
85
|
if (filters.agentId) {
|
|
86
|
-
conditions.push(
|
|
86
|
+
conditions.push("agent_id = ?");
|
|
87
87
|
params.push(filters.agentId);
|
|
88
88
|
}
|
|
89
89
|
if (filters.sessionId) {
|
|
90
|
-
conditions.push(
|
|
90
|
+
conditions.push("session_id = ?");
|
|
91
91
|
params.push(filters.sessionId);
|
|
92
92
|
}
|
|
93
93
|
if (filters.projectPath) {
|
|
94
|
-
conditions.push(
|
|
94
|
+
conditions.push("project_path = ?");
|
|
95
95
|
params.push(filters.projectPath);
|
|
96
96
|
}
|
|
97
97
|
|
|
@@ -102,7 +102,7 @@ export function queryUsageTimeSeries(
|
|
|
102
102
|
COALESCE(SUM(cost_usd), 0) AS cost_usd,
|
|
103
103
|
COALESCE(SUM(request_count), 0) AS request_count
|
|
104
104
|
FROM usage_events
|
|
105
|
-
WHERE ${conditions.join(
|
|
105
|
+
WHERE ${conditions.join(" AND ")}
|
|
106
106
|
GROUP BY bucket_start
|
|
107
107
|
ORDER BY bucket_start ASC
|
|
108
108
|
`;
|
|
@@ -114,7 +114,7 @@ export function queryUsageTimeSeries(
|
|
|
114
114
|
request_count: number;
|
|
115
115
|
}>;
|
|
116
116
|
|
|
117
|
-
return rows.map(r => ({
|
|
117
|
+
return rows.map((r) => ({
|
|
118
118
|
bucketStart: r.bucket_start,
|
|
119
119
|
tokens: r.tokens,
|
|
120
120
|
costUsd: r.cost_usd,
|
|
@@ -122,17 +122,22 @@ export function queryUsageTimeSeries(
|
|
|
122
122
|
}));
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
-
export function calculateBurnRate(
|
|
125
|
+
export function calculateBurnRate(
|
|
126
|
+
windowMs: number,
|
|
127
|
+
nowMs = Date.now(),
|
|
128
|
+
): { costPerHour: number; tokensPerMinute: number } {
|
|
126
129
|
const db = getDatabase();
|
|
127
130
|
const startMs = nowMs - windowMs;
|
|
128
131
|
|
|
129
|
-
const row = db
|
|
132
|
+
const row = db
|
|
133
|
+
.prepare(`
|
|
130
134
|
SELECT
|
|
131
135
|
COALESCE(SUM(cost_usd), 0) AS cost,
|
|
132
136
|
COALESCE(SUM(input_tokens + output_tokens), 0) AS tokens
|
|
133
137
|
FROM usage_events
|
|
134
138
|
WHERE timestamp >= ? AND timestamp <= ?
|
|
135
|
-
`)
|
|
139
|
+
`)
|
|
140
|
+
.get(startMs, nowMs) as { cost: number; tokens: number };
|
|
136
141
|
|
|
137
142
|
const hours = windowMs / (1000 * 60 * 60);
|
|
138
143
|
const minutes = windowMs / (1000 * 60);
|
|
@@ -143,7 +148,10 @@ export function calculateBurnRate(windowMs: number, nowMs = Date.now()): { costP
|
|
|
143
148
|
};
|
|
144
149
|
}
|
|
145
150
|
|
|
146
|
-
export function getTotalUsageInWindow(
|
|
151
|
+
export function getTotalUsageInWindow(
|
|
152
|
+
startMs: number,
|
|
153
|
+
endMs: number,
|
|
154
|
+
): {
|
|
147
155
|
inputTokens: number;
|
|
148
156
|
outputTokens: number;
|
|
149
157
|
costUsd: number;
|
|
@@ -151,7 +159,8 @@ export function getTotalUsageInWindow(startMs: number, endMs: number): {
|
|
|
151
159
|
} {
|
|
152
160
|
const db = getDatabase();
|
|
153
161
|
|
|
154
|
-
const row = db
|
|
162
|
+
const row = db
|
|
163
|
+
.prepare(`
|
|
155
164
|
SELECT
|
|
156
165
|
COALESCE(SUM(input_tokens), 0) AS input_tokens,
|
|
157
166
|
COALESCE(SUM(output_tokens), 0) AS output_tokens,
|
|
@@ -159,7 +168,8 @@ export function getTotalUsageInWindow(startMs: number, endMs: number): {
|
|
|
159
168
|
COALESCE(SUM(request_count), 0) AS request_count
|
|
160
169
|
FROM usage_events
|
|
161
170
|
WHERE timestamp >= ? AND timestamp <= ?
|
|
162
|
-
`)
|
|
171
|
+
`)
|
|
172
|
+
.get(startMs, endMs) as {
|
|
163
173
|
input_tokens: number;
|
|
164
174
|
output_tokens: number;
|
|
165
175
|
cost_usd: number;
|
|
@@ -190,7 +200,7 @@ export interface ProviderDailyCost {
|
|
|
190
200
|
export function queryProviderDailyCosts(
|
|
191
201
|
startMs: number,
|
|
192
202
|
endMs: number,
|
|
193
|
-
bucketMs: number
|
|
203
|
+
bucketMs: number,
|
|
194
204
|
): ProviderDailyCost[] {
|
|
195
205
|
const db = getDatabase();
|
|
196
206
|
|
|
@@ -216,7 +226,7 @@ export function queryProviderDailyCosts(
|
|
|
216
226
|
request_count: number;
|
|
217
227
|
}>;
|
|
218
228
|
|
|
219
|
-
return rows.map(r => ({
|
|
229
|
+
return rows.map((r) => ({
|
|
220
230
|
provider: r.provider,
|
|
221
231
|
bucketStart: r.bucket_start,
|
|
222
232
|
costUsd: r.cost_usd,
|
|
@@ -236,7 +246,7 @@ export interface ModelDailyCost {
|
|
|
236
246
|
export function queryModelDailyCosts(
|
|
237
247
|
startMs: number,
|
|
238
248
|
endMs: number,
|
|
239
|
-
bucketMs: number
|
|
249
|
+
bucketMs: number,
|
|
240
250
|
): ModelDailyCost[] {
|
|
241
251
|
const db = getDatabase();
|
|
242
252
|
|
|
@@ -262,7 +272,7 @@ export function queryModelDailyCosts(
|
|
|
262
272
|
request_count: number;
|
|
263
273
|
}>;
|
|
264
274
|
|
|
265
|
-
return rows.map(r => ({
|
|
275
|
+
return rows.map((r) => ({
|
|
266
276
|
model: r.model,
|
|
267
277
|
bucketStart: r.bucket_start,
|
|
268
278
|
costUsd: r.cost_usd,
|
|
@@ -282,7 +292,7 @@ export interface ProjectDailyCost {
|
|
|
282
292
|
export function queryProjectDailyCosts(
|
|
283
293
|
startMs: number,
|
|
284
294
|
endMs: number,
|
|
285
|
-
bucketMs: number
|
|
295
|
+
bucketMs: number,
|
|
286
296
|
): ProjectDailyCost[] {
|
|
287
297
|
const db = getDatabase();
|
|
288
298
|
|
|
@@ -308,7 +318,7 @@ export function queryProjectDailyCosts(
|
|
|
308
318
|
request_count: number;
|
|
309
319
|
}>;
|
|
310
320
|
|
|
311
|
-
return rows.map(r => ({
|
|
321
|
+
return rows.map((r) => ({
|
|
312
322
|
projectPath: r.project_path,
|
|
313
323
|
bucketStart: r.bucket_start,
|
|
314
324
|
costUsd: r.cost_usd,
|
|
@@ -320,14 +330,16 @@ export function queryProjectDailyCosts(
|
|
|
320
330
|
export function getSessionActivityTimeline(sessionId: string): SessionActivityPoint[] {
|
|
321
331
|
const db = getDatabase();
|
|
322
332
|
|
|
323
|
-
const rows = db
|
|
333
|
+
const rows = db
|
|
334
|
+
.prepare(`
|
|
324
335
|
SELECT timestamp, (input_tokens + output_tokens) AS tokens
|
|
325
336
|
FROM usage_events
|
|
326
337
|
WHERE session_id = ?
|
|
327
338
|
ORDER BY timestamp ASC
|
|
328
|
-
`)
|
|
339
|
+
`)
|
|
340
|
+
.all(sessionId) as Array<{ timestamp: number; tokens: number }>;
|
|
329
341
|
|
|
330
|
-
return rows.map(r => ({
|
|
342
|
+
return rows.map((r) => ({
|
|
331
343
|
timestamp: r.timestamp,
|
|
332
344
|
tokens: r.tokens,
|
|
333
345
|
}));
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data retention policy — prevents unbounded database growth.
|
|
3
|
+
*
|
|
4
|
+
* Detailed snapshot data (agent_session_snapshots, agent_session_stream_snapshots,
|
|
5
|
+
* provider_snapshots) is pruned after a retention window. Aggregated data in
|
|
6
|
+
* usage_events, hourly_aggregates, and daily_aggregates is kept longer since
|
|
7
|
+
* those tables grow much more slowly.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { getDatabase, isDatabaseInitialized } from "./db.ts";
|
|
11
|
+
|
|
12
|
+
/** Keep detailed snapshots for 7 days */
|
|
13
|
+
const SNAPSHOT_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
|
|
14
|
+
|
|
15
|
+
/** Keep provider snapshots for 7 days */
|
|
16
|
+
const PROVIDER_SNAPSHOT_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
|
|
17
|
+
|
|
18
|
+
/** Keep usage events for 90 days */
|
|
19
|
+
const USAGE_EVENT_RETENTION_MS = 90 * 24 * 60 * 60 * 1000;
|
|
20
|
+
|
|
21
|
+
/** Maximum rows to delete in a single batch (prevents long-running transactions) */
|
|
22
|
+
const PRUNE_BATCH_SIZE = 10_000;
|
|
23
|
+
|
|
24
|
+
export interface PruneResult {
|
|
25
|
+
agentSessionSnapshots: number;
|
|
26
|
+
providerSnapshots: number;
|
|
27
|
+
usageEvents: number;
|
|
28
|
+
durationMs: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Prune old data from the database. Deletes in batches to avoid
|
|
33
|
+
* holding a write lock for too long.
|
|
34
|
+
*/
|
|
35
|
+
export function pruneOldData(nowMs: number = Date.now()): PruneResult {
|
|
36
|
+
if (!isDatabaseInitialized()) {
|
|
37
|
+
return { agentSessionSnapshots: 0, providerSnapshots: 0, usageEvents: 0, durationMs: 0 };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const db = getDatabase();
|
|
41
|
+
const start = performance.now();
|
|
42
|
+
|
|
43
|
+
let agentSessionSnapshots = 0;
|
|
44
|
+
let providerSnapshots = 0;
|
|
45
|
+
let usageEvents = 0;
|
|
46
|
+
|
|
47
|
+
const snapshotCutoff = nowMs - SNAPSHOT_RETENTION_MS;
|
|
48
|
+
const providerCutoff = nowMs - PROVIDER_SNAPSHOT_RETENTION_MS;
|
|
49
|
+
const usageEventCutoff = nowMs - USAGE_EVENT_RETENTION_MS;
|
|
50
|
+
|
|
51
|
+
// Prune agent_session_snapshots in batches.
|
|
52
|
+
// CASCADE delete handles agent_session_stream_snapshots automatically.
|
|
53
|
+
{
|
|
54
|
+
const stmt = db.prepare(`
|
|
55
|
+
DELETE FROM agent_session_snapshots
|
|
56
|
+
WHERE id IN (
|
|
57
|
+
SELECT id FROM agent_session_snapshots
|
|
58
|
+
WHERE timestamp < ?
|
|
59
|
+
LIMIT ?
|
|
60
|
+
)
|
|
61
|
+
`);
|
|
62
|
+
|
|
63
|
+
let deleted: number;
|
|
64
|
+
do {
|
|
65
|
+
const result = stmt.run(snapshotCutoff, PRUNE_BATCH_SIZE);
|
|
66
|
+
deleted = result.changes;
|
|
67
|
+
agentSessionSnapshots += deleted;
|
|
68
|
+
} while (deleted === PRUNE_BATCH_SIZE);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Prune old provider_snapshots in batches
|
|
72
|
+
{
|
|
73
|
+
const stmt = db.prepare(`
|
|
74
|
+
DELETE FROM provider_snapshots
|
|
75
|
+
WHERE id IN (
|
|
76
|
+
SELECT id FROM provider_snapshots
|
|
77
|
+
WHERE timestamp < ?
|
|
78
|
+
LIMIT ?
|
|
79
|
+
)
|
|
80
|
+
`);
|
|
81
|
+
|
|
82
|
+
let deleted: number;
|
|
83
|
+
do {
|
|
84
|
+
const result = stmt.run(providerCutoff, PRUNE_BATCH_SIZE);
|
|
85
|
+
deleted = result.changes;
|
|
86
|
+
providerSnapshots += deleted;
|
|
87
|
+
} while (deleted === PRUNE_BATCH_SIZE);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Prune old usage_events in batches
|
|
91
|
+
{
|
|
92
|
+
const stmt = db.prepare(`
|
|
93
|
+
DELETE FROM usage_events
|
|
94
|
+
WHERE id IN (
|
|
95
|
+
SELECT id FROM usage_events
|
|
96
|
+
WHERE timestamp < ?
|
|
97
|
+
LIMIT ?
|
|
98
|
+
)
|
|
99
|
+
`);
|
|
100
|
+
|
|
101
|
+
let deleted: number;
|
|
102
|
+
do {
|
|
103
|
+
const result = stmt.run(usageEventCutoff, PRUNE_BATCH_SIZE);
|
|
104
|
+
deleted = result.changes;
|
|
105
|
+
usageEvents += deleted;
|
|
106
|
+
} while (deleted === PRUNE_BATCH_SIZE);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const durationMs = Math.round(performance.now() - start);
|
|
110
|
+
|
|
111
|
+
return { agentSessionSnapshots, providerSnapshots, usageEvents, durationMs };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Run incremental auto_vacuum if supported (WAL mode).
|
|
116
|
+
* This reclaims free pages without blocking readers.
|
|
117
|
+
*/
|
|
118
|
+
export function incrementalVacuum(pages = 1000): void {
|
|
119
|
+
if (!isDatabaseInitialized()) return;
|
|
120
|
+
|
|
121
|
+
const db = getDatabase();
|
|
122
|
+
try {
|
|
123
|
+
db.exec(`PRAGMA incremental_vacuum(${pages})`);
|
|
124
|
+
} catch {
|
|
125
|
+
// Not all builds support incremental vacuum — silently skip
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Get the database file size in bytes. Returns 0 if unable to determine.
|
|
131
|
+
*/
|
|
132
|
+
export function getDatabaseSizeBytes(): number {
|
|
133
|
+
if (!isDatabaseInitialized()) return 0;
|
|
134
|
+
|
|
135
|
+
const db = getDatabase();
|
|
136
|
+
try {
|
|
137
|
+
const row = db.prepare("PRAGMA page_count").get() as { page_count: number } | null;
|
|
138
|
+
const sizeRow = db.prepare("PRAGMA page_size").get() as { page_size: number } | null;
|
|
139
|
+
if (row && sizeRow) {
|
|
140
|
+
return row.page_count * sizeRow.page_size;
|
|
141
|
+
}
|
|
142
|
+
} catch {
|
|
143
|
+
// ignore
|
|
144
|
+
}
|
|
145
|
+
return 0;
|
|
146
|
+
}
|
package/src/storage/types.ts
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
// Enums & Literal Types
|
|
8
8
|
// ============================================================================
|
|
9
9
|
|
|
10
|
-
export type UsageEventSource =
|
|
11
|
-
export type SessionStatus =
|
|
12
|
-
export type CostSource =
|
|
13
|
-
export type PricingSource =
|
|
10
|
+
export type UsageEventSource = "agent" | "provider";
|
|
11
|
+
export type SessionStatus = "active" | "idle";
|
|
12
|
+
export type CostSource = "api" | "estimated";
|
|
13
|
+
export type PricingSource = "models.dev" | "fallback" | "unknown";
|
|
14
14
|
|
|
15
15
|
// ============================================================================
|
|
16
16
|
// App Runs Table
|
|
@@ -253,7 +253,7 @@ export interface StreamTotals {
|
|
|
253
253
|
*/
|
|
254
254
|
export function computeStreamDelta(
|
|
255
255
|
current: StreamTotals,
|
|
256
|
-
previous: StreamTotals | undefined
|
|
256
|
+
previous: StreamTotals | undefined,
|
|
257
257
|
): StreamTotals | null {
|
|
258
258
|
if (!previous) {
|
|
259
259
|
// First observation - treat as a delta from zero
|