@tokentop/ttop 0.4.0 → 0.6.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 +7 -6
- package/package.json +12 -5
- package/src/agents/aggregator.test.ts +306 -0
- package/src/agents/aggregator.ts +44 -2
- package/src/agents/types.ts +3 -0
- package/src/cli.ts +16 -2
- package/src/demo/simulator.ts +2 -2
- package/src/plugins/agents/index.ts +3 -0
- package/src/plugins/auth-sources.ts +2 -3
- package/src/plugins/lifecycle.ts +2 -2
- package/src/plugins/loader.test.ts +1 -1
- package/src/plugins/loader.ts +15 -7
- package/src/plugins/notification-bus.ts +5 -2
- package/src/plugins/notifications/terminal-bell.ts +1 -1
- package/src/plugins/notifications/visual-flash.ts +1 -1
- package/src/plugins/plugin-context-factory.ts +4 -19
- package/src/plugins/plugin-host.test.ts +197 -0
- package/src/plugins/plugin-host.ts +45 -1
- package/src/plugins/providers/anthropic.test.ts +202 -0
- package/src/plugins/providers/anthropic.ts +21 -2
- package/src/plugins/providers/antigravity.ts +17 -1
- package/src/plugins/providers/chutes.ts +205 -0
- package/src/plugins/providers/codex.ts +17 -1
- package/src/plugins/providers/gemini.ts +17 -1
- package/src/plugins/providers/github-copilot.ts +17 -1
- package/src/plugins/providers/index.ts +1 -0
- package/src/plugins/providers/minimax.ts +17 -1
- package/src/plugins/providers/openai-api.ts +53 -1
- package/src/plugins/providers/opencode-zen.ts +1 -1
- package/src/plugins/providers/perplexity.ts +11 -2
- package/src/plugins/providers/zai.ts +17 -1
- package/src/plugins/registry.ts +9 -9
- package/src/plugins/sandbox-guard.test.ts +1 -1
- package/src/plugins/sandbox-guard.ts +1 -1
- package/src/plugins/sandbox.ts +1 -1
- package/src/plugins/themes/catppuccin-latte.ts +1 -1
- package/src/plugins/themes/catppuccin-mocha.ts +1 -1
- package/src/plugins/themes/claude-code.ts +1 -1
- package/src/plugins/themes/dracula.ts +1 -1
- package/src/plugins/themes/github-light.ts +1 -1
- package/src/plugins/themes/gruvbox-dark.ts +1 -1
- package/src/plugins/themes/gruvbox-light.ts +1 -1
- package/src/plugins/themes/kanagawa.ts +1 -1
- package/src/plugins/themes/nord.ts +1 -1
- package/src/plugins/themes/one-dark.ts +1 -1
- package/src/plugins/themes/opencode.ts +1 -1
- package/src/plugins/themes/rose-pine-dawn.ts +1 -1
- package/src/plugins/themes/rose-pine.ts +1 -1
- package/src/plugins/themes/solarized-light.ts +1 -1
- package/src/plugins/themes/tokyo-night.ts +1 -1
- package/src/plugins/update-checker.ts +2 -2
- package/src/pricing/estimator.test.ts +1 -1
- package/src/pricing/estimator.ts +1 -1
- package/src/pricing/fallback.ts +5 -1
- package/src/pricing/index.ts +1 -1
- package/src/pricing/models-dev.ts +1 -1
- package/src/storage/index.ts +6 -0
- package/src/storage/migrations/index.ts +17 -0
- package/src/storage/repos/pluginStorage.ts +35 -0
- package/src/tui/App.tsx +17 -9
- package/src/tui/components/HelpOverlay.tsx +1 -1
- package/src/tui/components/LimitGauge.tsx +12 -4
- package/src/tui/components/ProviderCard.tsx +39 -23
- package/src/tui/components/ProviderDetailPanel.tsx +1 -1
- package/src/tui/components/ProviderLimitsPanel.tsx +5 -0
- package/src/tui/components/SessionDetailsDrawer.tsx +10 -0
- package/src/tui/components/SessionsTable.tsx +62 -37
- package/src/tui/components/SettingsModal.tsx +1 -1
- package/src/tui/components/Skeleton.tsx +9 -6
- package/src/tui/components/SortOverlay.tsx +167 -0
- package/src/tui/components/Spinner.tsx +16 -11
- package/src/tui/components/ThemePicker.tsx +1 -1
- package/src/tui/contexts/AgentSessionContext.tsx +70 -17
- package/src/tui/contexts/PluginContext.tsx +72 -14
- package/src/tui/contexts/RealTimeActivityContext.tsx +33 -20
- package/src/tui/contexts/ThemeContext.tsx +7 -4
- package/src/tui/createApp.tsx +2 -0
- package/src/tui/hooks/index.ts +1 -0
- package/src/tui/hooks/useAnimatedValue.ts +29 -26
- package/src/tui/hooks/useAnimationTick.ts +93 -0
- package/src/tui/hooks/useDashboardKeyboard.ts +11 -4
- package/src/tui/hooks/useEntranceAnimation.ts +16 -10
- package/src/tui/hooks/usePulse.ts +13 -7
- package/src/tui/hooks/useSessionEnrichedProviders.ts +1 -1
- package/src/tui/hooks/useValueFlash.ts +25 -33
- package/src/tui/index.tsx +4 -0
- package/src/tui/types/projectSort.ts +35 -0
- package/src/tui/types/sort.ts +61 -0
- package/src/tui/views/ProjectsView.tsx +104 -38
- package/src/tui/views/RealTimeDashboard.tsx +91 -8
- package/src/tui/views/SettingsView.tsx +1 -1
- package/src/utils/__tests__/error-category.test.ts +147 -0
- package/src/utils/currency.ts +4 -2
- package/src/utils/error-category.ts +91 -0
- package/src/version.ts +1 -1
- package/src/plugins/types/agent.ts +0 -108
- package/src/plugins/types/base.ts +0 -278
- package/src/plugins/types/index.ts +0 -19
- package/src/plugins/types/notification.ts +0 -51
- package/src/plugins/types/provider.ts +0 -237
- package/src/plugins/types/theme.ts +0 -78
- package/src/storage/database.ts +0 -623
package/src/storage/database.ts
DELETED
|
@@ -1,623 +0,0 @@
|
|
|
1
|
-
import { Database } from "bun:sqlite";
|
|
2
|
-
import * as fs from "fs/promises";
|
|
3
|
-
import { PATHS } from "./paths.ts";
|
|
4
|
-
|
|
5
|
-
let db: Database | null = null;
|
|
6
|
-
|
|
7
|
-
const SCHEMA = `
|
|
8
|
-
CREATE TABLE IF NOT EXISTS usage (
|
|
9
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
10
|
-
timestamp INTEGER NOT NULL,
|
|
11
|
-
provider TEXT NOT NULL,
|
|
12
|
-
model TEXT NOT NULL,
|
|
13
|
-
agent TEXT,
|
|
14
|
-
session_id TEXT,
|
|
15
|
-
input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
16
|
-
output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
17
|
-
cache_read_tokens INTEGER DEFAULT 0,
|
|
18
|
-
cache_write_tokens INTEGER DEFAULT 0,
|
|
19
|
-
cost_usd REAL NOT NULL DEFAULT 0,
|
|
20
|
-
project_path TEXT,
|
|
21
|
-
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
CREATE INDEX IF NOT EXISTS idx_usage_timestamp ON usage(timestamp);
|
|
25
|
-
CREATE INDEX IF NOT EXISTS idx_usage_provider ON usage(provider, timestamp);
|
|
26
|
-
CREATE INDEX IF NOT EXISTS idx_usage_model ON usage(model, timestamp);
|
|
27
|
-
CREATE INDEX IF NOT EXISTS idx_usage_agent ON usage(agent, timestamp);
|
|
28
|
-
CREATE INDEX IF NOT EXISTS idx_usage_session ON usage(session_id);
|
|
29
|
-
|
|
30
|
-
CREATE TABLE IF NOT EXISTS provider_snapshots (
|
|
31
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
32
|
-
timestamp INTEGER NOT NULL,
|
|
33
|
-
provider TEXT NOT NULL,
|
|
34
|
-
used_percent REAL,
|
|
35
|
-
limit_reached INTEGER DEFAULT 0,
|
|
36
|
-
tokens_input INTEGER,
|
|
37
|
-
tokens_output INTEGER,
|
|
38
|
-
cost_usd REAL,
|
|
39
|
-
raw_json TEXT,
|
|
40
|
-
created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now'))
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
CREATE INDEX IF NOT EXISTS idx_snapshots_provider ON provider_snapshots(provider, timestamp);
|
|
44
|
-
|
|
45
|
-
CREATE TABLE IF NOT EXISTS daily_aggregates (
|
|
46
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
47
|
-
date TEXT NOT NULL,
|
|
48
|
-
provider TEXT NOT NULL,
|
|
49
|
-
model TEXT,
|
|
50
|
-
total_input_tokens INTEGER NOT NULL DEFAULT 0,
|
|
51
|
-
total_output_tokens INTEGER NOT NULL DEFAULT 0,
|
|
52
|
-
total_cache_read INTEGER DEFAULT 0,
|
|
53
|
-
total_cache_write INTEGER DEFAULT 0,
|
|
54
|
-
total_cost_usd REAL NOT NULL DEFAULT 0,
|
|
55
|
-
request_count INTEGER NOT NULL DEFAULT 0,
|
|
56
|
-
UNIQUE(date, provider, model)
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
CREATE INDEX IF NOT EXISTS idx_daily_date ON daily_aggregates(date);
|
|
60
|
-
CREATE INDEX IF NOT EXISTS idx_daily_provider ON daily_aggregates(provider, date);
|
|
61
|
-
|
|
62
|
-
CREATE TABLE IF NOT EXISTS plugin_storage (
|
|
63
|
-
plugin_id TEXT NOT NULL,
|
|
64
|
-
key TEXT NOT NULL,
|
|
65
|
-
value TEXT NOT NULL,
|
|
66
|
-
updated_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
|
|
67
|
-
PRIMARY KEY (plugin_id, key)
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
CREATE INDEX IF NOT EXISTS idx_plugin_storage_plugin ON plugin_storage(plugin_id);
|
|
71
|
-
`;
|
|
72
|
-
|
|
73
|
-
export async function initDatabase(): Promise<Database> {
|
|
74
|
-
if (db) return db;
|
|
75
|
-
|
|
76
|
-
await fs.mkdir(PATHS.data.dir, { recursive: true });
|
|
77
|
-
|
|
78
|
-
db = new Database(PATHS.data.database, { create: true });
|
|
79
|
-
db.exec("PRAGMA journal_mode = WAL");
|
|
80
|
-
db.exec("PRAGMA synchronous = NORMAL");
|
|
81
|
-
db.exec(SCHEMA);
|
|
82
|
-
|
|
83
|
-
return db;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export function getDatabase(): Database {
|
|
87
|
-
if (!db) {
|
|
88
|
-
throw new Error("Database not initialized. Call initDatabase() first.");
|
|
89
|
-
}
|
|
90
|
-
return db;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export function closeDatabase(): void {
|
|
94
|
-
if (db) {
|
|
95
|
-
db.close();
|
|
96
|
-
db = null;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export interface UsageRecord {
|
|
101
|
-
timestamp: number;
|
|
102
|
-
provider: string;
|
|
103
|
-
model: string;
|
|
104
|
-
agent?: string | undefined;
|
|
105
|
-
sessionId?: string | undefined;
|
|
106
|
-
inputTokens: number;
|
|
107
|
-
outputTokens: number;
|
|
108
|
-
cacheReadTokens?: number | undefined;
|
|
109
|
-
cacheWriteTokens?: number | undefined;
|
|
110
|
-
costUsd: number;
|
|
111
|
-
projectPath?: string | undefined;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export interface ProviderSnapshot {
|
|
115
|
-
timestamp: number;
|
|
116
|
-
provider: string;
|
|
117
|
-
usedPercent?: number | undefined;
|
|
118
|
-
limitReached?: boolean | undefined;
|
|
119
|
-
tokensInput?: number | undefined;
|
|
120
|
-
tokensOutput?: number | undefined;
|
|
121
|
-
costUsd?: number | undefined;
|
|
122
|
-
rawJson?: string | undefined;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
export interface DailyAggregate {
|
|
126
|
-
date: string;
|
|
127
|
-
provider: string;
|
|
128
|
-
model?: string | undefined;
|
|
129
|
-
totalInputTokens: number;
|
|
130
|
-
totalOutputTokens: number;
|
|
131
|
-
totalCacheRead: number;
|
|
132
|
-
totalCacheWrite: number;
|
|
133
|
-
totalCostUsd: number;
|
|
134
|
-
requestCount: number;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export interface UsageQuery {
|
|
138
|
-
startTime?: number;
|
|
139
|
-
endTime?: number;
|
|
140
|
-
provider?: string;
|
|
141
|
-
model?: string;
|
|
142
|
-
agent?: string;
|
|
143
|
-
sessionId?: string;
|
|
144
|
-
limit?: number;
|
|
145
|
-
offset?: number;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export function insertUsage(record: UsageRecord): void {
|
|
149
|
-
const db = getDatabase();
|
|
150
|
-
const stmt = db.prepare(`
|
|
151
|
-
INSERT INTO usage (
|
|
152
|
-
timestamp, provider, model, agent, session_id,
|
|
153
|
-
input_tokens, output_tokens, cache_read_tokens, cache_write_tokens,
|
|
154
|
-
cost_usd, project_path
|
|
155
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
156
|
-
`);
|
|
157
|
-
|
|
158
|
-
stmt.run(
|
|
159
|
-
record.timestamp,
|
|
160
|
-
record.provider,
|
|
161
|
-
record.model,
|
|
162
|
-
record.agent ?? null,
|
|
163
|
-
record.sessionId ?? null,
|
|
164
|
-
record.inputTokens,
|
|
165
|
-
record.outputTokens,
|
|
166
|
-
record.cacheReadTokens ?? 0,
|
|
167
|
-
record.cacheWriteTokens ?? 0,
|
|
168
|
-
record.costUsd,
|
|
169
|
-
record.projectPath ?? null,
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
export function insertUsageBatch(records: UsageRecord[]): void {
|
|
174
|
-
const db = getDatabase();
|
|
175
|
-
const stmt = db.prepare(`
|
|
176
|
-
INSERT INTO usage (
|
|
177
|
-
timestamp, provider, model, agent, session_id,
|
|
178
|
-
input_tokens, output_tokens, cache_read_tokens, cache_write_tokens,
|
|
179
|
-
cost_usd, project_path
|
|
180
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
181
|
-
`);
|
|
182
|
-
|
|
183
|
-
const transaction = db.transaction((records: UsageRecord[]) => {
|
|
184
|
-
for (const record of records) {
|
|
185
|
-
stmt.run(
|
|
186
|
-
record.timestamp,
|
|
187
|
-
record.provider,
|
|
188
|
-
record.model,
|
|
189
|
-
record.agent ?? null,
|
|
190
|
-
record.sessionId ?? null,
|
|
191
|
-
record.inputTokens,
|
|
192
|
-
record.outputTokens,
|
|
193
|
-
record.cacheReadTokens ?? 0,
|
|
194
|
-
record.cacheWriteTokens ?? 0,
|
|
195
|
-
record.costUsd,
|
|
196
|
-
record.projectPath ?? null,
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
transaction(records);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export function insertProviderSnapshot(snapshot: ProviderSnapshot): void {
|
|
205
|
-
const db = getDatabase();
|
|
206
|
-
const stmt = db.prepare(`
|
|
207
|
-
INSERT INTO provider_snapshots (
|
|
208
|
-
timestamp, provider, used_percent, limit_reached,
|
|
209
|
-
tokens_input, tokens_output, cost_usd, raw_json
|
|
210
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
211
|
-
`);
|
|
212
|
-
|
|
213
|
-
stmt.run(
|
|
214
|
-
snapshot.timestamp,
|
|
215
|
-
snapshot.provider,
|
|
216
|
-
snapshot.usedPercent ?? null,
|
|
217
|
-
snapshot.limitReached ? 1 : 0,
|
|
218
|
-
snapshot.tokensInput ?? null,
|
|
219
|
-
snapshot.tokensOutput ?? null,
|
|
220
|
-
snapshot.costUsd ?? null,
|
|
221
|
-
snapshot.rawJson ?? null,
|
|
222
|
-
);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
export function queryUsage(query: UsageQuery): UsageRecord[] {
|
|
226
|
-
const db = getDatabase();
|
|
227
|
-
const conditions: string[] = [];
|
|
228
|
-
const params: (string | number)[] = [];
|
|
229
|
-
|
|
230
|
-
if (query.startTime !== undefined) {
|
|
231
|
-
conditions.push("timestamp >= ?");
|
|
232
|
-
params.push(query.startTime);
|
|
233
|
-
}
|
|
234
|
-
if (query.endTime !== undefined) {
|
|
235
|
-
conditions.push("timestamp <= ?");
|
|
236
|
-
params.push(query.endTime);
|
|
237
|
-
}
|
|
238
|
-
if (query.provider) {
|
|
239
|
-
conditions.push("provider = ?");
|
|
240
|
-
params.push(query.provider);
|
|
241
|
-
}
|
|
242
|
-
if (query.model) {
|
|
243
|
-
conditions.push("model = ?");
|
|
244
|
-
params.push(query.model);
|
|
245
|
-
}
|
|
246
|
-
if (query.agent) {
|
|
247
|
-
conditions.push("agent = ?");
|
|
248
|
-
params.push(query.agent);
|
|
249
|
-
}
|
|
250
|
-
if (query.sessionId) {
|
|
251
|
-
conditions.push("session_id = ?");
|
|
252
|
-
params.push(query.sessionId);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
256
|
-
const limitClause = query.limit ? `LIMIT ${query.limit}` : "";
|
|
257
|
-
const offsetClause = query.offset ? `OFFSET ${query.offset}` : "";
|
|
258
|
-
|
|
259
|
-
const sql = `
|
|
260
|
-
SELECT * FROM usage
|
|
261
|
-
${whereClause}
|
|
262
|
-
ORDER BY timestamp DESC
|
|
263
|
-
${limitClause} ${offsetClause}
|
|
264
|
-
`;
|
|
265
|
-
|
|
266
|
-
const rows = db.prepare(sql).all(...params) as Array<{
|
|
267
|
-
timestamp: number;
|
|
268
|
-
provider: string;
|
|
269
|
-
model: string;
|
|
270
|
-
agent: string | null;
|
|
271
|
-
session_id: string | null;
|
|
272
|
-
input_tokens: number;
|
|
273
|
-
output_tokens: number;
|
|
274
|
-
cache_read_tokens: number;
|
|
275
|
-
cache_write_tokens: number;
|
|
276
|
-
cost_usd: number;
|
|
277
|
-
project_path: string | null;
|
|
278
|
-
}>;
|
|
279
|
-
|
|
280
|
-
return rows.map((row) => ({
|
|
281
|
-
timestamp: row.timestamp,
|
|
282
|
-
provider: row.provider,
|
|
283
|
-
model: row.model,
|
|
284
|
-
agent: row.agent ?? undefined,
|
|
285
|
-
sessionId: row.session_id ?? undefined,
|
|
286
|
-
inputTokens: row.input_tokens,
|
|
287
|
-
outputTokens: row.output_tokens,
|
|
288
|
-
cacheReadTokens: row.cache_read_tokens,
|
|
289
|
-
cacheWriteTokens: row.cache_write_tokens,
|
|
290
|
-
costUsd: row.cost_usd,
|
|
291
|
-
projectPath: row.project_path ?? undefined,
|
|
292
|
-
}));
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
export interface UsageSummary {
|
|
296
|
-
totalInputTokens: number;
|
|
297
|
-
totalOutputTokens: number;
|
|
298
|
-
totalCacheRead: number;
|
|
299
|
-
totalCacheWrite: number;
|
|
300
|
-
totalCost: number;
|
|
301
|
-
requestCount: number;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
export function getUsageSummary(startTime: number, endTime: number): UsageSummary {
|
|
305
|
-
const db = getDatabase();
|
|
306
|
-
const row = db
|
|
307
|
-
.prepare(`
|
|
308
|
-
SELECT
|
|
309
|
-
COALESCE(SUM(input_tokens), 0) as total_input,
|
|
310
|
-
COALESCE(SUM(output_tokens), 0) as total_output,
|
|
311
|
-
COALESCE(SUM(cache_read_tokens), 0) as total_cache_read,
|
|
312
|
-
COALESCE(SUM(cache_write_tokens), 0) as total_cache_write,
|
|
313
|
-
COALESCE(SUM(cost_usd), 0) as total_cost,
|
|
314
|
-
COUNT(*) as request_count
|
|
315
|
-
FROM usage
|
|
316
|
-
WHERE timestamp >= ? AND timestamp <= ?
|
|
317
|
-
`)
|
|
318
|
-
.get(startTime, endTime) as {
|
|
319
|
-
total_input: number;
|
|
320
|
-
total_output: number;
|
|
321
|
-
total_cache_read: number;
|
|
322
|
-
total_cache_write: number;
|
|
323
|
-
total_cost: number;
|
|
324
|
-
request_count: number;
|
|
325
|
-
};
|
|
326
|
-
|
|
327
|
-
return {
|
|
328
|
-
totalInputTokens: row.total_input,
|
|
329
|
-
totalOutputTokens: row.total_output,
|
|
330
|
-
totalCacheRead: row.total_cache_read,
|
|
331
|
-
totalCacheWrite: row.total_cache_write,
|
|
332
|
-
totalCost: row.total_cost,
|
|
333
|
-
requestCount: row.request_count,
|
|
334
|
-
};
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
export interface ProviderSummary {
|
|
338
|
-
provider: string;
|
|
339
|
-
totalInputTokens: number;
|
|
340
|
-
totalOutputTokens: number;
|
|
341
|
-
totalCost: number;
|
|
342
|
-
requestCount: number;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
export function getUsageByProvider(startTime: number, endTime: number): ProviderSummary[] {
|
|
346
|
-
const db = getDatabase();
|
|
347
|
-
const rows = db
|
|
348
|
-
.prepare(`
|
|
349
|
-
SELECT
|
|
350
|
-
provider,
|
|
351
|
-
COALESCE(SUM(input_tokens), 0) as total_input,
|
|
352
|
-
COALESCE(SUM(output_tokens), 0) as total_output,
|
|
353
|
-
COALESCE(SUM(cost_usd), 0) as total_cost,
|
|
354
|
-
COUNT(*) as request_count
|
|
355
|
-
FROM usage
|
|
356
|
-
WHERE timestamp >= ? AND timestamp <= ?
|
|
357
|
-
GROUP BY provider
|
|
358
|
-
ORDER BY total_cost DESC
|
|
359
|
-
`)
|
|
360
|
-
.all(startTime, endTime) as Array<{
|
|
361
|
-
provider: string;
|
|
362
|
-
total_input: number;
|
|
363
|
-
total_output: number;
|
|
364
|
-
total_cost: number;
|
|
365
|
-
request_count: number;
|
|
366
|
-
}>;
|
|
367
|
-
|
|
368
|
-
return rows.map((row) => ({
|
|
369
|
-
provider: row.provider,
|
|
370
|
-
totalInputTokens: row.total_input,
|
|
371
|
-
totalOutputTokens: row.total_output,
|
|
372
|
-
totalCost: row.total_cost,
|
|
373
|
-
requestCount: row.request_count,
|
|
374
|
-
}));
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
export interface ModelSummary {
|
|
378
|
-
model: string;
|
|
379
|
-
provider: string;
|
|
380
|
-
totalInputTokens: number;
|
|
381
|
-
totalOutputTokens: number;
|
|
382
|
-
totalCost: number;
|
|
383
|
-
requestCount: number;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
export function getUsageByModel(startTime: number, endTime: number): ModelSummary[] {
|
|
387
|
-
const db = getDatabase();
|
|
388
|
-
const rows = db
|
|
389
|
-
.prepare(`
|
|
390
|
-
SELECT
|
|
391
|
-
model,
|
|
392
|
-
provider,
|
|
393
|
-
COALESCE(SUM(input_tokens), 0) as total_input,
|
|
394
|
-
COALESCE(SUM(output_tokens), 0) as total_output,
|
|
395
|
-
COALESCE(SUM(cost_usd), 0) as total_cost,
|
|
396
|
-
COUNT(*) as request_count
|
|
397
|
-
FROM usage
|
|
398
|
-
WHERE timestamp >= ? AND timestamp <= ?
|
|
399
|
-
GROUP BY model, provider
|
|
400
|
-
ORDER BY total_cost DESC
|
|
401
|
-
`)
|
|
402
|
-
.all(startTime, endTime) as Array<{
|
|
403
|
-
model: string;
|
|
404
|
-
provider: string;
|
|
405
|
-
total_input: number;
|
|
406
|
-
total_output: number;
|
|
407
|
-
total_cost: number;
|
|
408
|
-
request_count: number;
|
|
409
|
-
}>;
|
|
410
|
-
|
|
411
|
-
return rows.map((row) => ({
|
|
412
|
-
model: row.model,
|
|
413
|
-
provider: row.provider,
|
|
414
|
-
totalInputTokens: row.total_input,
|
|
415
|
-
totalOutputTokens: row.total_output,
|
|
416
|
-
totalCost: row.total_cost,
|
|
417
|
-
requestCount: row.request_count,
|
|
418
|
-
}));
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
export interface TimeSeriesPoint {
|
|
422
|
-
timestamp: number;
|
|
423
|
-
totalTokens: number;
|
|
424
|
-
totalCost: number;
|
|
425
|
-
requestCount: number;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
export function getUsageTimeSeries(
|
|
429
|
-
startTime: number,
|
|
430
|
-
endTime: number,
|
|
431
|
-
bucketMinutes: number = 5,
|
|
432
|
-
): TimeSeriesPoint[] {
|
|
433
|
-
const db = getDatabase();
|
|
434
|
-
const bucketSeconds = bucketMinutes * 60;
|
|
435
|
-
|
|
436
|
-
const rows = db
|
|
437
|
-
.prepare(`
|
|
438
|
-
SELECT
|
|
439
|
-
(timestamp / ? * ?) as bucket_time,
|
|
440
|
-
COALESCE(SUM(input_tokens + output_tokens), 0) as total_tokens,
|
|
441
|
-
COALESCE(SUM(cost_usd), 0) as total_cost,
|
|
442
|
-
COUNT(*) as request_count
|
|
443
|
-
FROM usage
|
|
444
|
-
WHERE timestamp >= ? AND timestamp <= ?
|
|
445
|
-
GROUP BY bucket_time
|
|
446
|
-
ORDER BY bucket_time ASC
|
|
447
|
-
`)
|
|
448
|
-
.all(bucketSeconds, bucketSeconds, startTime, endTime) as Array<{
|
|
449
|
-
bucket_time: number;
|
|
450
|
-
total_tokens: number;
|
|
451
|
-
total_cost: number;
|
|
452
|
-
request_count: number;
|
|
453
|
-
}>;
|
|
454
|
-
|
|
455
|
-
return rows.map((row) => ({
|
|
456
|
-
timestamp: row.bucket_time,
|
|
457
|
-
totalTokens: row.total_tokens,
|
|
458
|
-
totalCost: row.total_cost,
|
|
459
|
-
requestCount: row.request_count,
|
|
460
|
-
}));
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
export function getProviderSnapshots(
|
|
464
|
-
provider: string,
|
|
465
|
-
startTime: number,
|
|
466
|
-
endTime: number,
|
|
467
|
-
limit: number = 100,
|
|
468
|
-
): ProviderSnapshot[] {
|
|
469
|
-
const db = getDatabase();
|
|
470
|
-
const rows = db
|
|
471
|
-
.prepare(`
|
|
472
|
-
SELECT * FROM provider_snapshots
|
|
473
|
-
WHERE provider = ? AND timestamp >= ? AND timestamp <= ?
|
|
474
|
-
ORDER BY timestamp DESC
|
|
475
|
-
LIMIT ?
|
|
476
|
-
`)
|
|
477
|
-
.all(provider, startTime, endTime, limit) as Array<{
|
|
478
|
-
timestamp: number;
|
|
479
|
-
provider: string;
|
|
480
|
-
used_percent: number | null;
|
|
481
|
-
limit_reached: number;
|
|
482
|
-
tokens_input: number | null;
|
|
483
|
-
tokens_output: number | null;
|
|
484
|
-
cost_usd: number | null;
|
|
485
|
-
raw_json: string | null;
|
|
486
|
-
}>;
|
|
487
|
-
|
|
488
|
-
return rows.map((row) => ({
|
|
489
|
-
timestamp: row.timestamp,
|
|
490
|
-
provider: row.provider,
|
|
491
|
-
usedPercent: row.used_percent ?? undefined,
|
|
492
|
-
limitReached: row.limit_reached === 1,
|
|
493
|
-
tokensInput: row.tokens_input ?? undefined,
|
|
494
|
-
tokensOutput: row.tokens_output ?? undefined,
|
|
495
|
-
costUsd: row.cost_usd ?? undefined,
|
|
496
|
-
rawJson: row.raw_json ?? undefined,
|
|
497
|
-
}));
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
export function upsertDailyAggregate(aggregate: DailyAggregate): void {
|
|
501
|
-
const db = getDatabase();
|
|
502
|
-
const stmt = db.prepare(`
|
|
503
|
-
INSERT INTO daily_aggregates (
|
|
504
|
-
date, provider, model,
|
|
505
|
-
total_input_tokens, total_output_tokens,
|
|
506
|
-
total_cache_read, total_cache_write,
|
|
507
|
-
total_cost_usd, request_count
|
|
508
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
509
|
-
ON CONFLICT(date, provider, model) DO UPDATE SET
|
|
510
|
-
total_input_tokens = total_input_tokens + excluded.total_input_tokens,
|
|
511
|
-
total_output_tokens = total_output_tokens + excluded.total_output_tokens,
|
|
512
|
-
total_cache_read = total_cache_read + excluded.total_cache_read,
|
|
513
|
-
total_cache_write = total_cache_write + excluded.total_cache_write,
|
|
514
|
-
total_cost_usd = total_cost_usd + excluded.total_cost_usd,
|
|
515
|
-
request_count = request_count + excluded.request_count
|
|
516
|
-
`);
|
|
517
|
-
|
|
518
|
-
stmt.run(
|
|
519
|
-
aggregate.date,
|
|
520
|
-
aggregate.provider,
|
|
521
|
-
aggregate.model ?? null,
|
|
522
|
-
aggregate.totalInputTokens,
|
|
523
|
-
aggregate.totalOutputTokens,
|
|
524
|
-
aggregate.totalCacheRead,
|
|
525
|
-
aggregate.totalCacheWrite,
|
|
526
|
-
aggregate.totalCostUsd,
|
|
527
|
-
aggregate.requestCount,
|
|
528
|
-
);
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
export function getDailyAggregates(
|
|
532
|
-
startDate: string,
|
|
533
|
-
endDate: string,
|
|
534
|
-
provider?: string,
|
|
535
|
-
): DailyAggregate[] {
|
|
536
|
-
const db = getDatabase();
|
|
537
|
-
const conditions = ["date >= ?", "date <= ?"];
|
|
538
|
-
const params: string[] = [startDate, endDate];
|
|
539
|
-
|
|
540
|
-
if (provider) {
|
|
541
|
-
conditions.push("provider = ?");
|
|
542
|
-
params.push(provider);
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
const rows = db
|
|
546
|
-
.prepare(`
|
|
547
|
-
SELECT * FROM daily_aggregates
|
|
548
|
-
WHERE ${conditions.join(" AND ")}
|
|
549
|
-
ORDER BY date DESC, total_cost_usd DESC
|
|
550
|
-
`)
|
|
551
|
-
.all(...params) as Array<{
|
|
552
|
-
date: string;
|
|
553
|
-
provider: string;
|
|
554
|
-
model: string | null;
|
|
555
|
-
total_input_tokens: number;
|
|
556
|
-
total_output_tokens: number;
|
|
557
|
-
total_cache_read: number;
|
|
558
|
-
total_cache_write: number;
|
|
559
|
-
total_cost_usd: number;
|
|
560
|
-
request_count: number;
|
|
561
|
-
}>;
|
|
562
|
-
|
|
563
|
-
return rows.map((row) => ({
|
|
564
|
-
date: row.date,
|
|
565
|
-
provider: row.provider,
|
|
566
|
-
model: row.model ?? undefined,
|
|
567
|
-
totalInputTokens: row.total_input_tokens,
|
|
568
|
-
totalOutputTokens: row.total_output_tokens,
|
|
569
|
-
totalCacheRead: row.total_cache_read,
|
|
570
|
-
totalCacheWrite: row.total_cache_write,
|
|
571
|
-
totalCostUsd: row.total_cost_usd,
|
|
572
|
-
requestCount: row.request_count,
|
|
573
|
-
}));
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
export function getTodaySummary(): UsageSummary {
|
|
577
|
-
const now = Date.now();
|
|
578
|
-
const startOfDay = new Date();
|
|
579
|
-
startOfDay.setHours(0, 0, 0, 0);
|
|
580
|
-
return getUsageSummary(startOfDay.getTime(), now);
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
export function getThisMonthSummary(): UsageSummary {
|
|
584
|
-
const now = Date.now();
|
|
585
|
-
const startOfMonth = new Date();
|
|
586
|
-
startOfMonth.setDate(1);
|
|
587
|
-
startOfMonth.setHours(0, 0, 0, 0);
|
|
588
|
-
return getUsageSummary(startOfMonth.getTime(), now);
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
// ---------------------------------------------------------------------------
|
|
592
|
-
// Plugin KV storage
|
|
593
|
-
// ---------------------------------------------------------------------------
|
|
594
|
-
|
|
595
|
-
export function pluginStorageGet(pluginId: string, key: string): string | null {
|
|
596
|
-
const db = getDatabase();
|
|
597
|
-
const row = db
|
|
598
|
-
.prepare("SELECT value FROM plugin_storage WHERE plugin_id = ? AND key = ?")
|
|
599
|
-
.get(pluginId, key) as { value: string } | null;
|
|
600
|
-
return row?.value ?? null;
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
export function pluginStorageSet(pluginId: string, key: string, value: string): void {
|
|
604
|
-
const db = getDatabase();
|
|
605
|
-
db.prepare(
|
|
606
|
-
`INSERT INTO plugin_storage (plugin_id, key, value, updated_at)
|
|
607
|
-
VALUES (?, ?, ?, ?)
|
|
608
|
-
ON CONFLICT(plugin_id, key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`,
|
|
609
|
-
).run(pluginId, key, value, Math.floor(Date.now() / 1000));
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
export function pluginStorageDelete(pluginId: string, key: string): void {
|
|
613
|
-
const db = getDatabase();
|
|
614
|
-
db.prepare("DELETE FROM plugin_storage WHERE plugin_id = ? AND key = ?").run(pluginId, key);
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
export function pluginStorageHas(pluginId: string, key: string): boolean {
|
|
618
|
-
const db = getDatabase();
|
|
619
|
-
const row = db
|
|
620
|
-
.prepare("SELECT 1 FROM plugin_storage WHERE plugin_id = ? AND key = ?")
|
|
621
|
-
.get(pluginId, key);
|
|
622
|
-
return row !== null && row !== undefined;
|
|
623
|
-
}
|