@hasna/economy 0.3.7 → 0.3.9
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 +33 -4
- package/README.md +28 -61
- package/dashboard/README.md +17 -64
- package/docs/README.md +12 -0
- package/docs/cli.md +88 -0
- package/docs/configuration.md +88 -0
- package/docs/ingestion.md +47 -0
- package/docs/mcp.md +65 -0
- package/docs/otel.md +53 -0
- package/docs/rest-api.md +89 -0
- package/package.json +6 -7
- package/dist/cli/brains.d.ts +0 -3
- package/dist/cli/brains.d.ts.map +0 -1
- package/dist/cli/commands/brief.d.ts +0 -11
- package/dist/cli/commands/brief.d.ts.map +0 -1
- package/dist/cli/commands/completion.d.ts +0 -2
- package/dist/cli/commands/completion.d.ts.map +0 -1
- package/dist/cli/commands/extras.d.ts +0 -4
- package/dist/cli/commands/extras.d.ts.map +0 -1
- package/dist/cli/commands/menubar.d.ts +0 -7
- package/dist/cli/commands/menubar.d.ts.map +0 -1
- package/dist/cli/commands/notification.d.ts +0 -8
- package/dist/cli/commands/notification.d.ts.map +0 -1
- package/dist/cli/commands/todos.d.ts +0 -29
- package/dist/cli/commands/todos.d.ts.map +0 -1
- package/dist/cli/commands/tui.d.ts +0 -26
- package/dist/cli/commands/tui.d.ts.map +0 -1
- package/dist/cli/commands/watch.d.ts +0 -10
- package/dist/cli/commands/watch.d.ts.map +0 -1
- package/dist/cli/index.d.ts +0 -3
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/cli/index.js +0 -12304
- package/dist/db/cloud.d.ts +0 -28
- package/dist/db/cloud.d.ts.map +0 -1
- package/dist/db/database.d.ts +0 -150
- package/dist/db/database.d.ts.map +0 -1
- package/dist/db/pg-migrations.d.ts +0 -7
- package/dist/db/pg-migrations.d.ts.map +0 -1
- package/dist/db/pg-sync-worker.d.ts +0 -2
- package/dist/db/pg-sync-worker.d.ts.map +0 -1
- package/dist/db/sync-pg.d.ts +0 -43
- package/dist/db/sync-pg.d.ts.map +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -3729
- package/dist/ingest/billing.d.ts +0 -27
- package/dist/ingest/billing.d.ts.map +0 -1
- package/dist/ingest/claude-quota.d.ts +0 -5
- package/dist/ingest/claude-quota.d.ts.map +0 -1
- package/dist/ingest/claude.d.ts +0 -18
- package/dist/ingest/claude.d.ts.map +0 -1
- package/dist/ingest/codex-quota.d.ts +0 -5
- package/dist/ingest/codex-quota.d.ts.map +0 -1
- package/dist/ingest/codex.d.ts +0 -8
- package/dist/ingest/codex.d.ts.map +0 -1
- package/dist/ingest/cursor.d.ts +0 -6
- package/dist/ingest/cursor.d.ts.map +0 -1
- package/dist/ingest/gemini.d.ts +0 -6
- package/dist/ingest/gemini.d.ts.map +0 -1
- package/dist/ingest/hermes.d.ts +0 -6
- package/dist/ingest/hermes.d.ts.map +0 -1
- package/dist/ingest/loops.d.ts +0 -6
- package/dist/ingest/loops.d.ts.map +0 -1
- package/dist/ingest/opencode.d.ts +0 -7
- package/dist/ingest/opencode.d.ts.map +0 -1
- package/dist/ingest/otel.d.ts +0 -33
- package/dist/ingest/otel.d.ts.map +0 -1
- package/dist/ingest/pi.d.ts +0 -7
- package/dist/ingest/pi.d.ts.map +0 -1
- package/dist/ingest/plugin.d.ts +0 -17
- package/dist/ingest/plugin.d.ts.map +0 -1
- package/dist/lib/accounts.d.ts +0 -11
- package/dist/lib/accounts.d.ts.map +0 -1
- package/dist/lib/agents.d.ts +0 -11
- package/dist/lib/agents.d.ts.map +0 -1
- package/dist/lib/analytics.d.ts +0 -68
- package/dist/lib/analytics.d.ts.map +0 -1
- package/dist/lib/billing-diff.d.ts +0 -22
- package/dist/lib/billing-diff.d.ts.map +0 -1
- package/dist/lib/brief.d.ts +0 -72
- package/dist/lib/brief.d.ts.map +0 -1
- package/dist/lib/cloud-storage.d.ts +0 -48
- package/dist/lib/cloud-storage.d.ts.map +0 -1
- package/dist/lib/config.d.ts +0 -13
- package/dist/lib/config.d.ts.map +0 -1
- package/dist/lib/contracts-client/index.d.ts +0 -3
- package/dist/lib/contracts-client/index.d.ts.map +0 -1
- package/dist/lib/contracts-client/mode.d.ts +0 -20
- package/dist/lib/contracts-client/mode.d.ts.map +0 -1
- package/dist/lib/contracts-client/storage.d.ts +0 -83
- package/dist/lib/contracts-client/storage.d.ts.map +0 -1
- package/dist/lib/contracts-client/transport.d.ts +0 -148
- package/dist/lib/contracts-client/transport.d.ts.map +0 -1
- package/dist/lib/gatherer.d.ts +0 -21
- package/dist/lib/gatherer.d.ts.map +0 -1
- package/dist/lib/model-config.d.ts +0 -8
- package/dist/lib/model-config.d.ts.map +0 -1
- package/dist/lib/open-projects.d.ts +0 -19
- package/dist/lib/open-projects.d.ts.map +0 -1
- package/dist/lib/package-metadata.d.ts +0 -8
- package/dist/lib/package-metadata.d.ts.map +0 -1
- package/dist/lib/paths.d.ts +0 -20
- package/dist/lib/paths.d.ts.map +0 -1
- package/dist/lib/periods.d.ts +0 -6
- package/dist/lib/periods.d.ts.map +0 -1
- package/dist/lib/pricing.d.ts +0 -13
- package/dist/lib/pricing.d.ts.map +0 -1
- package/dist/lib/savings.d.ts +0 -17
- package/dist/lib/savings.d.ts.map +0 -1
- package/dist/lib/serve-auth.d.ts +0 -4
- package/dist/lib/serve-auth.d.ts.map +0 -1
- package/dist/lib/spikes.d.ts +0 -18
- package/dist/lib/spikes.d.ts.map +0 -1
- package/dist/lib/store/index.d.ts +0 -303
- package/dist/lib/store/index.d.ts.map +0 -1
- package/dist/lib/sync-all.d.ts +0 -28
- package/dist/lib/sync-all.d.ts.map +0 -1
- package/dist/lib/watch-paths.d.ts +0 -3
- package/dist/lib/watch-paths.d.ts.map +0 -1
- package/dist/lib/webhooks.d.ts +0 -3
- package/dist/lib/webhooks.d.ts.map +0 -1
- package/dist/mcp/http.d.ts +0 -14
- package/dist/mcp/http.d.ts.map +0 -1
- package/dist/mcp/index.d.ts +0 -3
- package/dist/mcp/index.d.ts.map +0 -1
- package/dist/mcp/index.js +0 -6193
- package/dist/mcp/server.d.ts +0 -4
- package/dist/mcp/server.d.ts.map +0 -1
- package/dist/openapi.d.ts +0 -3
- package/dist/openapi.d.ts.map +0 -1
- package/dist/otel/index.d.ts +0 -3
- package/dist/otel/index.d.ts.map +0 -1
- package/dist/otel/index.js +0 -1399
- package/dist/server/index.d.ts +0 -3
- package/dist/server/index.d.ts.map +0 -1
- package/dist/server/index.js +0 -7977
- package/dist/server/pg-sync-worker.js +0 -31
- package/dist/server/serve.d.ts +0 -42
- package/dist/server/serve.d.ts.map +0 -1
- package/dist/types/index.d.ts +0 -238
- package/dist/types/index.d.ts.map +0 -1
package/dist/index.js
DELETED
|
@@ -1,3729 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __returnValue = (v) => v;
|
|
4
|
-
function __exportSetter(name, newValue) {
|
|
5
|
-
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
-
}
|
|
7
|
-
var __export = (target, all) => {
|
|
8
|
-
for (var name in all)
|
|
9
|
-
__defProp(target, name, {
|
|
10
|
-
get: all[name],
|
|
11
|
-
enumerable: true,
|
|
12
|
-
configurable: true,
|
|
13
|
-
set: __exportSetter.bind(all, name)
|
|
14
|
-
});
|
|
15
|
-
};
|
|
16
|
-
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
|
-
|
|
18
|
-
// src/lib/pricing.ts
|
|
19
|
-
var exports_pricing = {};
|
|
20
|
-
__export(exports_pricing, {
|
|
21
|
-
normalizeModelName: () => normalizeModelName,
|
|
22
|
-
getPricingFromRows: () => getPricingFromRows,
|
|
23
|
-
getPricingFromDb: () => getPricingFromDb,
|
|
24
|
-
getPricing: () => getPricing,
|
|
25
|
-
estimateCostFromRows: () => estimateCostFromRows,
|
|
26
|
-
ensurePricingSeeded: () => ensurePricingSeeded,
|
|
27
|
-
computeCostFromDb: () => computeCostFromDb,
|
|
28
|
-
computeCost: () => computeCost,
|
|
29
|
-
DEFAULT_PRICING: () => DEFAULT_PRICING
|
|
30
|
-
});
|
|
31
|
-
function normalizeModelName(raw) {
|
|
32
|
-
return raw.trim().toLowerCase().replace(/^models\//, "").replace(/^[a-z0-9_.-]+\//, "").replace(/:.+$/, "").replace(/-\d{8}$/, "").replace(/-\d{4}-\d{2}-\d{2}$/, "");
|
|
33
|
-
}
|
|
34
|
-
function normalizeModelNamePreservingProvider(raw) {
|
|
35
|
-
return raw.trim().toLowerCase().replace(/^models\//, "").replace(/:.+$/, "").replace(/-\d{8}$/, "").replace(/-\d{4}-\d{2}-\d{2}$/, "");
|
|
36
|
-
}
|
|
37
|
-
function modelLookupKeys(raw) {
|
|
38
|
-
const withProvider = normalizeModelNamePreservingProvider(raw);
|
|
39
|
-
const withoutProvider = normalizeModelName(raw);
|
|
40
|
-
return withProvider === withoutProvider ? [withoutProvider] : [withProvider, withoutProvider];
|
|
41
|
-
}
|
|
42
|
-
function bestPrefixMatch(normalized, entries) {
|
|
43
|
-
let best = null;
|
|
44
|
-
for (const entry of entries) {
|
|
45
|
-
const [key] = entry;
|
|
46
|
-
if (normalized !== key && !normalized.startsWith(`${key}-`))
|
|
47
|
-
continue;
|
|
48
|
-
if (!best || key.length > best[0].length)
|
|
49
|
-
best = entry;
|
|
50
|
-
}
|
|
51
|
-
return best?.[1] ?? null;
|
|
52
|
-
}
|
|
53
|
-
function bestModelMatch(model, entries) {
|
|
54
|
-
for (const key of modelLookupKeys(model)) {
|
|
55
|
-
const match = bestPrefixMatch(key, entries);
|
|
56
|
-
if (match)
|
|
57
|
-
return match;
|
|
58
|
-
}
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
function exactModelMatch(model, entries) {
|
|
62
|
-
for (const key of modelLookupKeys(model)) {
|
|
63
|
-
const match = entries.find(([entryKey]) => entryKey === key);
|
|
64
|
-
if (match)
|
|
65
|
-
return match[1];
|
|
66
|
-
}
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
function ensurePricingSeeded(db) {
|
|
70
|
-
seedModelPricing(db, DEFAULT_PRICING);
|
|
71
|
-
repairLegacySeededPricing(db);
|
|
72
|
-
repairMissingDefaultCacheWrite1h(db);
|
|
73
|
-
repairMissingDefaultCacheStorage(db);
|
|
74
|
-
removeDeprecatedDefaultPricing(db);
|
|
75
|
-
}
|
|
76
|
-
function repairLegacySeededPricing(db) {
|
|
77
|
-
const now = new Date().toISOString();
|
|
78
|
-
const legacyModels = new Set([
|
|
79
|
-
...Object.keys(LEGACY_DEFAULT_PRICING),
|
|
80
|
-
...Object.keys(ADDITIONAL_LEGACY_DEFAULT_PRICING)
|
|
81
|
-
]);
|
|
82
|
-
for (const model of legacyModels) {
|
|
83
|
-
const current = getModelPricing(db, model);
|
|
84
|
-
const next = DEFAULT_PRICING[model];
|
|
85
|
-
if (!current || !next)
|
|
86
|
-
continue;
|
|
87
|
-
const legacy = LEGACY_DEFAULT_PRICING[model];
|
|
88
|
-
const legacyRows = [
|
|
89
|
-
...legacy ? [legacy] : [],
|
|
90
|
-
...ADDITIONAL_LEGACY_DEFAULT_PRICING[model] ?? []
|
|
91
|
-
];
|
|
92
|
-
if (!legacyRows.some((row) => samePricing(current, row)))
|
|
93
|
-
continue;
|
|
94
|
-
upsertModelPricing(db, {
|
|
95
|
-
model,
|
|
96
|
-
input_per_1m: next.inputPer1M,
|
|
97
|
-
output_per_1m: next.outputPer1M,
|
|
98
|
-
cache_read_per_1m: next.cacheReadPer1M,
|
|
99
|
-
cache_write_per_1m: next.cacheWritePer1M,
|
|
100
|
-
cache_write_1h_per_1m: next.cacheWrite1hPer1M ?? 0,
|
|
101
|
-
cache_storage_per_1m_hour: next.cacheStoragePer1MHour ?? 0,
|
|
102
|
-
updated_at: now
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
function repairMissingDefaultCacheWrite1h(db) {
|
|
107
|
-
const now = new Date().toISOString();
|
|
108
|
-
for (const [model, next] of Object.entries(DEFAULT_PRICING)) {
|
|
109
|
-
if (!next.cacheWrite1hPer1M)
|
|
110
|
-
continue;
|
|
111
|
-
const current = getModelPricing(db, model);
|
|
112
|
-
if (!current)
|
|
113
|
-
continue;
|
|
114
|
-
if ((current.cache_write_1h_per_1m ?? 0) !== 0)
|
|
115
|
-
continue;
|
|
116
|
-
if (!sameBasePricing(current, next))
|
|
117
|
-
continue;
|
|
118
|
-
upsertModelPricing(db, {
|
|
119
|
-
model,
|
|
120
|
-
input_per_1m: current.input_per_1m,
|
|
121
|
-
output_per_1m: current.output_per_1m,
|
|
122
|
-
cache_read_per_1m: current.cache_read_per_1m,
|
|
123
|
-
cache_write_per_1m: current.cache_write_per_1m,
|
|
124
|
-
cache_write_1h_per_1m: next.cacheWrite1hPer1M,
|
|
125
|
-
cache_storage_per_1m_hour: current.cache_storage_per_1m_hour ?? next.cacheStoragePer1MHour ?? 0,
|
|
126
|
-
updated_at: now
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
function repairMissingDefaultCacheStorage(db) {
|
|
131
|
-
const now = new Date().toISOString();
|
|
132
|
-
for (const [model, next] of Object.entries(DEFAULT_PRICING)) {
|
|
133
|
-
if (!next.cacheStoragePer1MHour)
|
|
134
|
-
continue;
|
|
135
|
-
const current = getModelPricing(db, model);
|
|
136
|
-
if (!current)
|
|
137
|
-
continue;
|
|
138
|
-
if ((current.cache_storage_per_1m_hour ?? 0) !== 0)
|
|
139
|
-
continue;
|
|
140
|
-
if (!sameBasePricing(current, next))
|
|
141
|
-
continue;
|
|
142
|
-
upsertModelPricing(db, {
|
|
143
|
-
model,
|
|
144
|
-
input_per_1m: current.input_per_1m,
|
|
145
|
-
output_per_1m: current.output_per_1m,
|
|
146
|
-
cache_read_per_1m: current.cache_read_per_1m,
|
|
147
|
-
cache_write_per_1m: current.cache_write_per_1m,
|
|
148
|
-
cache_write_1h_per_1m: current.cache_write_1h_per_1m ?? next.cacheWrite1hPer1M ?? 0,
|
|
149
|
-
cache_storage_per_1m_hour: next.cacheStoragePer1MHour,
|
|
150
|
-
updated_at: now
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
function removeDeprecatedDefaultPricing(db) {
|
|
155
|
-
for (const [model, removedRows] of Object.entries(REMOVED_DEFAULT_PRICING)) {
|
|
156
|
-
const current = getModelPricing(db, model);
|
|
157
|
-
if (!current)
|
|
158
|
-
continue;
|
|
159
|
-
if (!removedRows.some((row) => samePricing(current, row)))
|
|
160
|
-
continue;
|
|
161
|
-
deleteModelPricing(db, model);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
function sameBasePricing(row, pricing) {
|
|
165
|
-
return row.input_per_1m === pricing.inputPer1M && row.output_per_1m === pricing.outputPer1M && row.cache_read_per_1m === pricing.cacheReadPer1M && row.cache_write_per_1m === pricing.cacheWritePer1M;
|
|
166
|
-
}
|
|
167
|
-
function samePricing(row, pricing) {
|
|
168
|
-
return row.input_per_1m === pricing.inputPer1M && row.output_per_1m === pricing.outputPer1M && row.cache_read_per_1m === pricing.cacheReadPer1M && row.cache_write_per_1m === pricing.cacheWritePer1M && (row.cache_write_1h_per_1m ?? 0) === (pricing.cacheWrite1hPer1M ?? 0) && (row.cache_storage_per_1m_hour ?? 0) === (pricing.cacheStoragePer1MHour ?? 0);
|
|
169
|
-
}
|
|
170
|
-
function getPricingFromDb(db, model) {
|
|
171
|
-
if (isFreeModel(model))
|
|
172
|
-
return FREE_PRICING;
|
|
173
|
-
for (const key of modelLookupKeys(model)) {
|
|
174
|
-
const row = getModelPricing(db, key);
|
|
175
|
-
if (row)
|
|
176
|
-
return modelPricingFromDbRow(row);
|
|
177
|
-
}
|
|
178
|
-
const allRows = db.prepare(`SELECT * FROM model_pricing`).all();
|
|
179
|
-
const match = bestModelMatch(model, allRows.map((r) => [r.model, r]));
|
|
180
|
-
if (!match)
|
|
181
|
-
return null;
|
|
182
|
-
return modelPricingFromDbRow(match);
|
|
183
|
-
}
|
|
184
|
-
function modelPricingFromDbRow(row) {
|
|
185
|
-
const seeded = DEFAULT_PRICING[row.model];
|
|
186
|
-
const cacheWrite1hPer1M = seeded?.cacheWrite1hPer1M && (row.cache_write_1h_per_1m ?? 0) === 0 && sameBasePricing(row, seeded) ? seeded.cacheWrite1hPer1M : row.cache_write_1h_per_1m ?? 0;
|
|
187
|
-
return {
|
|
188
|
-
inputPer1M: row.input_per_1m,
|
|
189
|
-
outputPer1M: row.output_per_1m,
|
|
190
|
-
cacheReadPer1M: row.cache_read_per_1m,
|
|
191
|
-
cacheWritePer1M: row.cache_write_per_1m,
|
|
192
|
-
cacheWrite1hPer1M,
|
|
193
|
-
cacheStoragePer1MHour: row.cache_storage_per_1m_hour ?? seeded?.cacheStoragePer1MHour ?? 0
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
function getPricing(model) {
|
|
197
|
-
if (isFreeModel(model))
|
|
198
|
-
return FREE_PRICING;
|
|
199
|
-
return bestModelMatch(model, Object.entries(DEFAULT_PRICING));
|
|
200
|
-
}
|
|
201
|
-
function isFreeModel(model) {
|
|
202
|
-
return model.trim().toLowerCase().endsWith(":free");
|
|
203
|
-
}
|
|
204
|
-
function computeCost(model, inputTokens, outputTokens, cacheReadTokens = 0, cacheWriteTokens = 0, cacheWrite1hTokens = 0, cacheStorageTokenHours = 0) {
|
|
205
|
-
const pricing = getPricing(model);
|
|
206
|
-
if (!pricing)
|
|
207
|
-
return 0;
|
|
208
|
-
return computeCostWithPricing(model, pricing, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, cacheWrite1hTokens, cacheStorageTokenHours);
|
|
209
|
-
}
|
|
210
|
-
function computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens = 0, cacheWriteTokens = 0, cacheWrite1hTokens = 0, cacheStorageTokenHours = 0) {
|
|
211
|
-
const pricing = getPricingFromDb(db, model) ?? getPricing(model);
|
|
212
|
-
if (!pricing)
|
|
213
|
-
return 0;
|
|
214
|
-
return computeCostWithPricing(model, pricing, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, cacheWrite1hTokens, cacheStorageTokenHours);
|
|
215
|
-
}
|
|
216
|
-
function getPricingFromRows(rows, model) {
|
|
217
|
-
if (isFreeModel(model))
|
|
218
|
-
return FREE_PRICING;
|
|
219
|
-
for (const key of modelLookupKeys(model)) {
|
|
220
|
-
const row = rows.find((r) => r.model === key);
|
|
221
|
-
if (row)
|
|
222
|
-
return modelPricingFromDbRow(row);
|
|
223
|
-
}
|
|
224
|
-
const match = bestModelMatch(model, rows.map((r) => [r.model, r]));
|
|
225
|
-
if (!match)
|
|
226
|
-
return null;
|
|
227
|
-
return modelPricingFromDbRow(match);
|
|
228
|
-
}
|
|
229
|
-
function estimateCostFromRows(rows, model, inputTokens, outputTokens, cacheReadTokens = 0, cacheWriteTokens = 0, cacheWrite1hTokens = 0, cacheStorageTokenHours = 0) {
|
|
230
|
-
const pricing = getPricingFromRows(rows, model) ?? getPricing(model);
|
|
231
|
-
if (!pricing)
|
|
232
|
-
return 0;
|
|
233
|
-
return computeCostWithPricing(model, pricing, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, cacheWrite1hTokens, cacheStorageTokenHours);
|
|
234
|
-
}
|
|
235
|
-
function computeCostWithPricing(model, pricing, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, cacheWrite1hTokens, cacheStorageTokenHours) {
|
|
236
|
-
if (isFreeModel(model))
|
|
237
|
-
return 0;
|
|
238
|
-
let effective = pricing;
|
|
239
|
-
const promptTier = bestModelMatch(model, Object.entries(GEMINI_PROMPT_TIERS)) ?? bestModelMatch(model, Object.entries(QWEN_PROMPT_TIERS)) ?? bestModelMatch(model, Object.entries(MINIMAX_PROMPT_TIERS)) ?? bestModelMatch(model, Object.entries(XAI_PROMPT_TIERS)) ?? exactModelMatch(model, Object.entries(OPENAI_PROMPT_TIERS));
|
|
240
|
-
if (promptTier) {
|
|
241
|
-
const billablePromptTokens = inputTokens + cacheReadTokens + cacheWriteTokens + cacheWrite1hTokens;
|
|
242
|
-
if (billablePromptTokens > promptTier.threshold) {
|
|
243
|
-
effective = {
|
|
244
|
-
...pricing,
|
|
245
|
-
inputPer1M: promptTier.inputPer1M ?? pricing.inputPer1M * (promptTier.inputMultiplier ?? 1),
|
|
246
|
-
outputPer1M: promptTier.outputPer1M ?? pricing.outputPer1M * (promptTier.outputMultiplier ?? 1),
|
|
247
|
-
cacheReadPer1M: promptTier.cacheReadPer1M ?? pricing.cacheReadPer1M * (promptTier.cacheReadMultiplier ?? 1),
|
|
248
|
-
cacheWritePer1M: promptTier.cacheWritePer1M ?? pricing.cacheWritePer1M * (promptTier.cacheWriteMultiplier ?? 1)
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
return (inputTokens * effective.inputPer1M + outputTokens * effective.outputPer1M + cacheReadTokens * effective.cacheReadPer1M + cacheWriteTokens * effective.cacheWritePer1M + cacheWrite1hTokens * (effective.cacheWrite1hPer1M ?? effective.cacheWritePer1M) + cacheStorageTokenHours * (effective.cacheStoragePer1MHour ?? 0)) / 1e6;
|
|
253
|
-
}
|
|
254
|
-
var DEFAULT_PRICING, LEGACY_DEFAULT_PRICING, ADDITIONAL_LEGACY_DEFAULT_PRICING, REMOVED_DEFAULT_PRICING, FREE_PRICING, GEMINI_PROMPT_TIERS, OPENAI_PROMPT_TIERS, QWEN_PROMPT_TIERS, MINIMAX_PROMPT_TIERS, XAI_PROMPT_TIERS;
|
|
255
|
-
var init_pricing = __esm(() => {
|
|
256
|
-
init_database();
|
|
257
|
-
DEFAULT_PRICING = {
|
|
258
|
-
"claude-opus-4-8": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
|
|
259
|
-
"claude-opus-4-7": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
|
|
260
|
-
"claude-opus-4-6": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
|
|
261
|
-
"claude-opus-4-5": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25, cacheWrite1hPer1M: 10 },
|
|
262
|
-
"claude-opus-4-1": { inputPer1M: 15, outputPer1M: 75, cacheReadPer1M: 1.5, cacheWritePer1M: 18.75, cacheWrite1hPer1M: 30 },
|
|
263
|
-
"claude-opus-4": { inputPer1M: 15, outputPer1M: 75, cacheReadPer1M: 1.5, cacheWritePer1M: 18.75, cacheWrite1hPer1M: 30 },
|
|
264
|
-
"claude-sonnet-4-6": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 6 },
|
|
265
|
-
"claude-sonnet-4-5": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 6 },
|
|
266
|
-
"claude-sonnet-4": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 6 },
|
|
267
|
-
"claude-3-7-sonnet": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 6 },
|
|
268
|
-
"claude-haiku-4-5": { inputPer1M: 1, outputPer1M: 5, cacheReadPer1M: 0.1, cacheWritePer1M: 1.25, cacheWrite1hPer1M: 2 },
|
|
269
|
-
"claude-3-5-haiku": { inputPer1M: 0.8, outputPer1M: 4, cacheReadPer1M: 0.08, cacheWritePer1M: 1, cacheWrite1hPer1M: 1.6 },
|
|
270
|
-
"claude-3-opus": { inputPer1M: 15, outputPer1M: 75, cacheReadPer1M: 1.5, cacheWritePer1M: 18.75, cacheWrite1hPer1M: 30 },
|
|
271
|
-
"claude-3-haiku": { inputPer1M: 0.25, outputPer1M: 1.25, cacheReadPer1M: 0.03, cacheWritePer1M: 0.3, cacheWrite1hPer1M: 0.5 },
|
|
272
|
-
"gemini-3.1-pro-preview": { inputPer1M: 2, outputPer1M: 12, cacheReadPer1M: 0.2, cacheWritePer1M: 0, cacheStoragePer1MHour: 4.5 },
|
|
273
|
-
"gemini-3.1-flash-lite-preview": { inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0.025, cacheWritePer1M: 0, cacheStoragePer1MHour: 1 },
|
|
274
|
-
"gemini-3.1-flash-lite": { inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0.025, cacheWritePer1M: 0, cacheStoragePer1MHour: 1 },
|
|
275
|
-
"gemini-3-flash-preview": { inputPer1M: 0.5, outputPer1M: 3, cacheReadPer1M: 0.05, cacheWritePer1M: 0, cacheStoragePer1MHour: 1 },
|
|
276
|
-
"gemini-2.5-pro": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0.125, cacheWritePer1M: 0, cacheStoragePer1MHour: 4.5 },
|
|
277
|
-
"gemini-2.5-flash": { inputPer1M: 0.3, outputPer1M: 2.5, cacheReadPer1M: 0.03, cacheWritePer1M: 0, cacheStoragePer1MHour: 1 },
|
|
278
|
-
"gemini-2.5-flash-lite": { inputPer1M: 0.1, outputPer1M: 0.4, cacheReadPer1M: 0.01, cacheWritePer1M: 0, cacheStoragePer1MHour: 1 },
|
|
279
|
-
"gemini-2.0-flash": { inputPer1M: 0.1, outputPer1M: 0.4, cacheReadPer1M: 0.025, cacheWritePer1M: 0, cacheStoragePer1MHour: 1 },
|
|
280
|
-
"gemini-2.0-flash-lite": { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
281
|
-
"google/gemini-3.1-pro-preview": { inputPer1M: 2, outputPer1M: 12, cacheReadPer1M: 0.2, cacheWritePer1M: 0.375 },
|
|
282
|
-
"google/gemini-3.1-flash-lite-preview": { inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0.025, cacheWritePer1M: 0.08333333333333334 },
|
|
283
|
-
"google/gemini-3.1-flash-lite": { inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0.025, cacheWritePer1M: 0.08333333333333334 },
|
|
284
|
-
"google/gemini-3-flash-preview": { inputPer1M: 0.5, outputPer1M: 3, cacheReadPer1M: 0.05, cacheWritePer1M: 0.08333333333333334 },
|
|
285
|
-
"google/gemini-2.5-pro": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0.125, cacheWritePer1M: 0.375 },
|
|
286
|
-
"google/gemini-2.5-flash": { inputPer1M: 0.3, outputPer1M: 2.5, cacheReadPer1M: 0.03, cacheWritePer1M: 0.08333333333333334 },
|
|
287
|
-
"google/gemini-2.5-flash-lite": { inputPer1M: 0.1, outputPer1M: 0.4, cacheReadPer1M: 0.01, cacheWritePer1M: 0.08333333333333334 },
|
|
288
|
-
"gpt-5.5": { inputPer1M: 5, outputPer1M: 30, cacheReadPer1M: 0.5, cacheWritePer1M: 0 },
|
|
289
|
-
"gpt-5.5-pro": { inputPer1M: 30, outputPer1M: 180, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
290
|
-
"gpt-5.4": { inputPer1M: 2.5, outputPer1M: 15, cacheReadPer1M: 0.25, cacheWritePer1M: 0 },
|
|
291
|
-
"gpt-5.4-pro": { inputPer1M: 30, outputPer1M: 180, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
292
|
-
"gpt-5.4-mini": { inputPer1M: 0.75, outputPer1M: 4.5, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
|
|
293
|
-
"gpt-5.4-nano": { inputPer1M: 0.2, outputPer1M: 1.25, cacheReadPer1M: 0.02, cacheWritePer1M: 0 },
|
|
294
|
-
"gpt-5.3-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.175, cacheWritePer1M: 0 },
|
|
295
|
-
"gpt-5.2-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.175, cacheWritePer1M: 0 },
|
|
296
|
-
"gpt-5.2-chat-latest": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.175, cacheWritePer1M: 0 },
|
|
297
|
-
"gpt-5.2": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.175, cacheWritePer1M: 0 },
|
|
298
|
-
"gpt-5-codex": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0.125, cacheWritePer1M: 0 },
|
|
299
|
-
"gpt-5-mini": { inputPer1M: 0.25, outputPer1M: 2, cacheReadPer1M: 0.025, cacheWritePer1M: 0 },
|
|
300
|
-
"gpt-5": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0.125, cacheWritePer1M: 0 },
|
|
301
|
-
"gpt-4o": { inputPer1M: 2.5, outputPer1M: 10, cacheReadPer1M: 1.25, cacheWritePer1M: 0 },
|
|
302
|
-
"gpt-4o-mini": { inputPer1M: 0.15, outputPer1M: 0.6, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
|
|
303
|
-
o1: { inputPer1M: 15, outputPer1M: 60, cacheReadPer1M: 7.5, cacheWritePer1M: 0 },
|
|
304
|
-
"o1-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.55, cacheWritePer1M: 0 },
|
|
305
|
-
o3: { inputPer1M: 2, outputPer1M: 8, cacheReadPer1M: 0.5, cacheWritePer1M: 0 },
|
|
306
|
-
"o3-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.55, cacheWritePer1M: 0 },
|
|
307
|
-
"o4-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.275, cacheWritePer1M: 0 },
|
|
308
|
-
"qwen3.6-plus": { inputPer1M: 0.325, outputPer1M: 1.95, cacheReadPer1M: 0.0325, cacheWritePer1M: 0.40625 },
|
|
309
|
-
"qwen3.6-flash": { inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0.025, cacheWritePer1M: 0.3125 },
|
|
310
|
-
"qwen3.6-35b-a3b": { inputPer1M: 0.15, outputPer1M: 1, cacheReadPer1M: 0.05, cacheWritePer1M: 0 },
|
|
311
|
-
"qwen3.6-max-preview": { inputPer1M: 1.04, outputPer1M: 6.24, cacheReadPer1M: 0.104, cacheWritePer1M: 1.3 },
|
|
312
|
-
"qwen3.6-27b": { inputPer1M: 0.32, outputPer1M: 3.2, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
313
|
-
"qwen/qwen3.6-plus": { inputPer1M: 0.325, outputPer1M: 1.95, cacheReadPer1M: 0.0325, cacheWritePer1M: 0.40625 },
|
|
314
|
-
"qwen/qwen3.6-flash": { inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0.025, cacheWritePer1M: 0.3125 },
|
|
315
|
-
"qwen/qwen3.6-35b-a3b": { inputPer1M: 0.15, outputPer1M: 1, cacheReadPer1M: 0.05, cacheWritePer1M: 0 },
|
|
316
|
-
"qwen/qwen3.6-max-preview": { inputPer1M: 1.04, outputPer1M: 6.24, cacheReadPer1M: 0.104, cacheWritePer1M: 1.3 },
|
|
317
|
-
"qwen/qwen3.6-27b": { inputPer1M: 0.32, outputPer1M: 3.2, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
318
|
-
"minimax-m2.7": { inputPer1M: 0.3, outputPer1M: 1.2, cacheReadPer1M: 0.06, cacheWritePer1M: 0.375 },
|
|
319
|
-
"minimax-m2.7-highspeed": { inputPer1M: 0.6, outputPer1M: 2.4, cacheReadPer1M: 0.06, cacheWritePer1M: 0.375 },
|
|
320
|
-
"minimax/minimax-m2.7": { inputPer1M: 0.299, outputPer1M: 1.2, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
321
|
-
"minimax-m1": { inputPer1M: 0.4, outputPer1M: 2.2, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
322
|
-
"minimax/minimax-m1": { inputPer1M: 0.4, outputPer1M: 2.2, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
323
|
-
"grok-4.3": { inputPer1M: 1.25, outputPer1M: 2.5, cacheReadPer1M: 0.2, cacheWritePer1M: 0 },
|
|
324
|
-
"grok-latest": { inputPer1M: 1.25, outputPer1M: 2.5, cacheReadPer1M: 0.2, cacheWritePer1M: 0 },
|
|
325
|
-
"grok-4.20": { inputPer1M: 1.25, outputPer1M: 2.5, cacheReadPer1M: 0.2, cacheWritePer1M: 0 },
|
|
326
|
-
"grok-4-1-fast": { inputPer1M: 0.2, outputPer1M: 0.5, cacheReadPer1M: 0.05, cacheWritePer1M: 0 },
|
|
327
|
-
"grok-4-fast": { inputPer1M: 0.2, outputPer1M: 0.5, cacheReadPer1M: 0.05, cacheWritePer1M: 0 },
|
|
328
|
-
"grok-4": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.75, cacheWritePer1M: 0 },
|
|
329
|
-
"grok-code-fast-1": { inputPer1M: 0.2, outputPer1M: 1.5, cacheReadPer1M: 0.02, cacheWritePer1M: 0 },
|
|
330
|
-
"grok-code-fast": { inputPer1M: 0.2, outputPer1M: 1.5, cacheReadPer1M: 0.02, cacheWritePer1M: 0 },
|
|
331
|
-
"grok-3": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.75, cacheWritePer1M: 0 },
|
|
332
|
-
"grok-3-mini": { inputPer1M: 0.3, outputPer1M: 0.5, cacheReadPer1M: 0.07, cacheWritePer1M: 0 },
|
|
333
|
-
"glm-5.1": { inputPer1M: 1.4, outputPer1M: 4.4, cacheReadPer1M: 0.26, cacheWritePer1M: 0 },
|
|
334
|
-
"glm-5": { inputPer1M: 1, outputPer1M: 3.2, cacheReadPer1M: 0.2, cacheWritePer1M: 0 },
|
|
335
|
-
"z-ai/glm-5.1": { inputPer1M: 1.05, outputPer1M: 3.5, cacheReadPer1M: 0.525, cacheWritePer1M: 0 },
|
|
336
|
-
"z-ai/glm-5": { inputPer1M: 0.6, outputPer1M: 1.92, cacheReadPer1M: 0.12, cacheWritePer1M: 0 },
|
|
337
|
-
"kimi-k2.6": { inputPer1M: 0.95, outputPer1M: 4, cacheReadPer1M: 0.16, cacheWritePer1M: 0 },
|
|
338
|
-
"kimi-k2.5": { inputPer1M: 0.6, outputPer1M: 3, cacheReadPer1M: 0.1, cacheWritePer1M: 0 },
|
|
339
|
-
"kimi-k2": { inputPer1M: 0.6, outputPer1M: 2.5, cacheReadPer1M: 0.15, cacheWritePer1M: 0 },
|
|
340
|
-
"moonshotai/kimi-k2.6": { inputPer1M: 0.75, outputPer1M: 3.5, cacheReadPer1M: 0.15, cacheWritePer1M: 0 },
|
|
341
|
-
"moonshotai/kimi-k2.5": { inputPer1M: 0.44, outputPer1M: 2, cacheReadPer1M: 0.22, cacheWritePer1M: 0 },
|
|
342
|
-
"moonshotai/kimi-k2": { inputPer1M: 0.57, outputPer1M: 2.3, cacheReadPer1M: 0, cacheWritePer1M: 0 }
|
|
343
|
-
};
|
|
344
|
-
LEGACY_DEFAULT_PRICING = {
|
|
345
|
-
"claude-3-5-haiku": { inputPer1M: 1, outputPer1M: 5, cacheReadPer1M: 0.1, cacheWritePer1M: 1.25 },
|
|
346
|
-
"claude-opus-4": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25 },
|
|
347
|
-
"gemini-2.5-pro": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0.31, cacheWritePer1M: 0 },
|
|
348
|
-
"gemini-2.5-flash": { inputPer1M: 0.15, outputPer1M: 0.6, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
349
|
-
"gemini-2.0-flash": { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
350
|
-
"gpt-5.3-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
351
|
-
"gpt-5.2-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
352
|
-
"gpt-5-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
353
|
-
"gpt-5-mini": { inputPer1M: 0.3, outputPer1M: 1.2, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
|
|
354
|
-
"gpt-5.2": { inputPer1M: 2, outputPer1M: 8, cacheReadPer1M: 0.5, cacheWritePer1M: 0 },
|
|
355
|
-
"o1-mini": { inputPer1M: 3, outputPer1M: 12, cacheReadPer1M: 1.5, cacheWritePer1M: 0 },
|
|
356
|
-
"grok-3": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
357
|
-
"grok-3-mini": { inputPer1M: 0.3, outputPer1M: 0.5, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
358
|
-
"qwen3.6-plus": { inputPer1M: 0.8, outputPer1M: 2, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
359
|
-
"minimax-m2.7": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
360
|
-
"minimax-m2.7-highspeed": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
361
|
-
"minimax-m1": { inputPer1M: 0.2, outputPer1M: 1.1, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
362
|
-
"glm-5.1": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
363
|
-
"glm-5": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
364
|
-
"kimi-k2": { inputPer1M: 0.6, outputPer1M: 0.6, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
365
|
-
o3: { inputPer1M: 10, outputPer1M: 40, cacheReadPer1M: 2.5, cacheWritePer1M: 0 }
|
|
366
|
-
};
|
|
367
|
-
ADDITIONAL_LEGACY_DEFAULT_PRICING = {
|
|
368
|
-
"gemini-2.5-pro": [
|
|
369
|
-
{ inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0, cacheWritePer1M: 0 }
|
|
370
|
-
],
|
|
371
|
-
"qwen3.6-plus": [
|
|
372
|
-
{ inputPer1M: 0.325, outputPer1M: 1.95, cacheReadPer1M: 0, cacheWritePer1M: 0.40625 },
|
|
373
|
-
{ inputPer1M: 0.325, outputPer1M: 1.95, cacheReadPer1M: 0.05, cacheWritePer1M: 0.40625 }
|
|
374
|
-
],
|
|
375
|
-
"qwen3.6-flash": [
|
|
376
|
-
{ inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0, cacheWritePer1M: 0.3125 }
|
|
377
|
-
],
|
|
378
|
-
"qwen3.6-max-preview": [
|
|
379
|
-
{ inputPer1M: 1.04, outputPer1M: 6.24, cacheReadPer1M: 0, cacheWritePer1M: 1.3 },
|
|
380
|
-
{ inputPer1M: 1.04, outputPer1M: 6.24, cacheReadPer1M: 0.13, cacheWritePer1M: 1.3 }
|
|
381
|
-
],
|
|
382
|
-
"qwen/qwen3.6-plus": [
|
|
383
|
-
{ inputPer1M: 0.325, outputPer1M: 1.95, cacheReadPer1M: 0, cacheWritePer1M: 0.40625 },
|
|
384
|
-
{ inputPer1M: 0.325, outputPer1M: 1.95, cacheReadPer1M: 0.05, cacheWritePer1M: 0.40625 }
|
|
385
|
-
],
|
|
386
|
-
"qwen/qwen3.6-flash": [
|
|
387
|
-
{ inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0, cacheWritePer1M: 0.3125 }
|
|
388
|
-
],
|
|
389
|
-
"qwen/qwen3.6-max-preview": [
|
|
390
|
-
{ inputPer1M: 1.04, outputPer1M: 6.24, cacheReadPer1M: 0, cacheWritePer1M: 1.3 },
|
|
391
|
-
{ inputPer1M: 1.04, outputPer1M: 6.24, cacheReadPer1M: 0.13, cacheWritePer1M: 1.3 }
|
|
392
|
-
]
|
|
393
|
-
};
|
|
394
|
-
REMOVED_DEFAULT_PRICING = {
|
|
395
|
-
"claude-3-5-sonnet": [
|
|
396
|
-
{ inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 6 },
|
|
397
|
-
{ inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 0 }
|
|
398
|
-
],
|
|
399
|
-
"claude-3-sonnet": [
|
|
400
|
-
{ inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 6 },
|
|
401
|
-
{ inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 0 }
|
|
402
|
-
],
|
|
403
|
-
"gemini-3.1-pro": [
|
|
404
|
-
{ inputPer1M: 2, outputPer1M: 12, cacheReadPer1M: 0.2, cacheWritePer1M: 0 },
|
|
405
|
-
{ inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0.31, cacheWritePer1M: 0 }
|
|
406
|
-
],
|
|
407
|
-
"gemini-1.5-pro": [
|
|
408
|
-
{ inputPer1M: 1.25, outputPer1M: 5, cacheReadPer1M: 0, cacheWritePer1M: 0 }
|
|
409
|
-
],
|
|
410
|
-
"gemini-1.5-flash": [
|
|
411
|
-
{ inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 }
|
|
412
|
-
],
|
|
413
|
-
"gpt-5.3-chat": [
|
|
414
|
-
{ inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.175, cacheWritePer1M: 0 },
|
|
415
|
-
{ inputPer1M: 2, outputPer1M: 8, cacheReadPer1M: 0.5, cacheWritePer1M: 0 }
|
|
416
|
-
],
|
|
417
|
-
"qwen3.6": [
|
|
418
|
-
{ inputPer1M: 0.3, outputPer1M: 0.6, cacheReadPer1M: 0, cacheWritePer1M: 0 }
|
|
419
|
-
]
|
|
420
|
-
};
|
|
421
|
-
FREE_PRICING = {
|
|
422
|
-
inputPer1M: 0,
|
|
423
|
-
outputPer1M: 0,
|
|
424
|
-
cacheReadPer1M: 0,
|
|
425
|
-
cacheWritePer1M: 0,
|
|
426
|
-
cacheWrite1hPer1M: 0,
|
|
427
|
-
cacheStoragePer1MHour: 0
|
|
428
|
-
};
|
|
429
|
-
GEMINI_PROMPT_TIERS = {
|
|
430
|
-
"gemini-3.1-pro-preview": {
|
|
431
|
-
threshold: 200000,
|
|
432
|
-
inputPer1M: 4,
|
|
433
|
-
outputPer1M: 18,
|
|
434
|
-
cacheReadPer1M: 0.4
|
|
435
|
-
},
|
|
436
|
-
"gemini-2.5-pro": {
|
|
437
|
-
threshold: 200000,
|
|
438
|
-
inputPer1M: 2.5,
|
|
439
|
-
outputPer1M: 15,
|
|
440
|
-
cacheReadPer1M: 0.25
|
|
441
|
-
}
|
|
442
|
-
};
|
|
443
|
-
OPENAI_PROMPT_TIERS = {
|
|
444
|
-
"gpt-5.5": {
|
|
445
|
-
threshold: 272000,
|
|
446
|
-
inputMultiplier: 2,
|
|
447
|
-
outputMultiplier: 1.5,
|
|
448
|
-
cacheReadMultiplier: 2
|
|
449
|
-
},
|
|
450
|
-
"gpt-5.4-pro": {
|
|
451
|
-
threshold: 272000,
|
|
452
|
-
inputMultiplier: 2,
|
|
453
|
-
outputMultiplier: 1.5,
|
|
454
|
-
cacheReadMultiplier: 2
|
|
455
|
-
},
|
|
456
|
-
"gpt-5.4": {
|
|
457
|
-
threshold: 272000,
|
|
458
|
-
inputMultiplier: 2,
|
|
459
|
-
outputMultiplier: 1.5,
|
|
460
|
-
cacheReadMultiplier: 2
|
|
461
|
-
}
|
|
462
|
-
};
|
|
463
|
-
QWEN_PROMPT_TIERS = {
|
|
464
|
-
"qwen3.6-plus": {
|
|
465
|
-
threshold: 256000,
|
|
466
|
-
inputPer1M: 1.3,
|
|
467
|
-
outputPer1M: 3.9,
|
|
468
|
-
cacheReadPer1M: 0.13,
|
|
469
|
-
cacheWritePer1M: 1.625
|
|
470
|
-
},
|
|
471
|
-
"qwen3.6-flash": {
|
|
472
|
-
threshold: 256000,
|
|
473
|
-
inputPer1M: 1,
|
|
474
|
-
outputPer1M: 4,
|
|
475
|
-
cacheReadPer1M: 0.1,
|
|
476
|
-
cacheWritePer1M: 1.25
|
|
477
|
-
},
|
|
478
|
-
"qwen3.6-max-preview": {
|
|
479
|
-
threshold: 128000,
|
|
480
|
-
inputPer1M: 1.6,
|
|
481
|
-
outputPer1M: 9.6,
|
|
482
|
-
cacheReadPer1M: 0.16,
|
|
483
|
-
cacheWritePer1M: 2
|
|
484
|
-
}
|
|
485
|
-
};
|
|
486
|
-
MINIMAX_PROMPT_TIERS = {
|
|
487
|
-
"minimax/minimax-m1": {
|
|
488
|
-
threshold: Number.POSITIVE_INFINITY
|
|
489
|
-
},
|
|
490
|
-
"minimax-m1": {
|
|
491
|
-
threshold: 200000,
|
|
492
|
-
inputPer1M: 1.3
|
|
493
|
-
}
|
|
494
|
-
};
|
|
495
|
-
XAI_PROMPT_TIERS = {
|
|
496
|
-
"grok-4.3": {
|
|
497
|
-
threshold: 200000,
|
|
498
|
-
inputPer1M: 2.5,
|
|
499
|
-
outputPer1M: 5,
|
|
500
|
-
cacheReadPer1M: 0.4
|
|
501
|
-
},
|
|
502
|
-
"grok-latest": {
|
|
503
|
-
threshold: 200000,
|
|
504
|
-
inputPer1M: 2.5,
|
|
505
|
-
outputPer1M: 5,
|
|
506
|
-
cacheReadPer1M: 0.4
|
|
507
|
-
},
|
|
508
|
-
"grok-4.20": {
|
|
509
|
-
threshold: 200000,
|
|
510
|
-
inputPer1M: 2.5,
|
|
511
|
-
outputPer1M: 5,
|
|
512
|
-
cacheReadPer1M: 0.4
|
|
513
|
-
},
|
|
514
|
-
"grok-4-1-fast": {
|
|
515
|
-
threshold: 128000,
|
|
516
|
-
inputPer1M: 0.4,
|
|
517
|
-
outputPer1M: 1,
|
|
518
|
-
cacheReadPer1M: 0
|
|
519
|
-
},
|
|
520
|
-
"grok-4-fast": {
|
|
521
|
-
threshold: 128000,
|
|
522
|
-
inputPer1M: 0.4,
|
|
523
|
-
outputPer1M: 1,
|
|
524
|
-
cacheReadPer1M: 0
|
|
525
|
-
},
|
|
526
|
-
"grok-4": {
|
|
527
|
-
threshold: 128000,
|
|
528
|
-
inputPer1M: 6,
|
|
529
|
-
outputPer1M: 30,
|
|
530
|
-
cacheReadPer1M: 0
|
|
531
|
-
}
|
|
532
|
-
};
|
|
533
|
-
});
|
|
534
|
-
|
|
535
|
-
// src/db/database.ts
|
|
536
|
-
import { SqliteAdapter as Database } from "@hasna/cloud";
|
|
537
|
-
import { execFileSync } from "child_process";
|
|
538
|
-
import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
539
|
-
import { hostname, platform } from "os";
|
|
540
|
-
import { homedir } from "os";
|
|
541
|
-
import { join } from "path";
|
|
542
|
-
function normalizeMachineId(value) {
|
|
543
|
-
const id = value?.trim().toLowerCase().split(".")[0];
|
|
544
|
-
return id && id.length > 0 ? id : null;
|
|
545
|
-
}
|
|
546
|
-
function macHostMachineId() {
|
|
547
|
-
if (platform() !== "darwin")
|
|
548
|
-
return null;
|
|
549
|
-
for (const key of ["LocalHostName", "ComputerName", "HostName"]) {
|
|
550
|
-
try {
|
|
551
|
-
const value = execFileSync("/usr/sbin/scutil", ["--get", key], {
|
|
552
|
-
encoding: "utf8",
|
|
553
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
554
|
-
timeout: 1000
|
|
555
|
-
});
|
|
556
|
-
const id = normalizeMachineId(value);
|
|
557
|
-
if (id && id !== "mac" && id !== "localhost")
|
|
558
|
-
return id;
|
|
559
|
-
} catch {}
|
|
560
|
-
}
|
|
561
|
-
return null;
|
|
562
|
-
}
|
|
563
|
-
function getMachineId() {
|
|
564
|
-
const envMachine = normalizeMachineId(process.env["ECONOMY_MACHINE_ID"]);
|
|
565
|
-
if (envMachine)
|
|
566
|
-
return envMachine;
|
|
567
|
-
const hostMachine = normalizeMachineId(hostname()) ?? "unknown";
|
|
568
|
-
if (hostMachine === "mac" || hostMachine === "localhost")
|
|
569
|
-
return macHostMachineId() ?? hostMachine;
|
|
570
|
-
return hostMachine;
|
|
571
|
-
}
|
|
572
|
-
function getDataDir() {
|
|
573
|
-
const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
|
|
574
|
-
const newDir = join(home, ".hasna", "economy");
|
|
575
|
-
const oldDir = join(home, ".economy");
|
|
576
|
-
if (existsSync(oldDir) && !existsSync(newDir)) {
|
|
577
|
-
mkdirSync(newDir, { recursive: true });
|
|
578
|
-
for (const file of readdirSync(oldDir)) {
|
|
579
|
-
const oldPath = join(oldDir, file);
|
|
580
|
-
if (statSync(oldPath).isFile()) {
|
|
581
|
-
copyFileSync(oldPath, join(newDir, file));
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
mkdirSync(newDir, { recursive: true });
|
|
586
|
-
return newDir;
|
|
587
|
-
}
|
|
588
|
-
function getDbPath() {
|
|
589
|
-
if (process.env["HASNA_ECONOMY_DB_PATH"])
|
|
590
|
-
return process.env["HASNA_ECONOMY_DB_PATH"];
|
|
591
|
-
if (process.env["ECONOMY_DB"])
|
|
592
|
-
return process.env["ECONOMY_DB"];
|
|
593
|
-
return join(getDataDir(), "economy.db");
|
|
594
|
-
}
|
|
595
|
-
function isSqliteBusyError(error) {
|
|
596
|
-
const candidate = error;
|
|
597
|
-
const code = typeof candidate.code === "string" ? candidate.code : "";
|
|
598
|
-
const message = typeof candidate.message === "string" ? candidate.message : String(error);
|
|
599
|
-
return code === "SQLITE_BUSY" || code === "SQLITE_BUSY_RECOVERY" || /database is locked|SQLITE_BUSY/i.test(message);
|
|
600
|
-
}
|
|
601
|
-
function retryDelayMs(attempt) {
|
|
602
|
-
return Math.min(1000, 50 * 2 ** attempt);
|
|
603
|
-
}
|
|
604
|
-
function withSqliteBusyRetry(operation, context) {
|
|
605
|
-
const maxAttempts = 8;
|
|
606
|
-
let lastError;
|
|
607
|
-
for (let attempt = 0;attempt < maxAttempts; attempt++) {
|
|
608
|
-
try {
|
|
609
|
-
return operation();
|
|
610
|
-
} catch (error) {
|
|
611
|
-
if (!isSqliteBusyError(error))
|
|
612
|
-
throw error;
|
|
613
|
-
lastError = error;
|
|
614
|
-
if (attempt === maxAttempts - 1)
|
|
615
|
-
break;
|
|
616
|
-
Bun.sleepSync(retryDelayMs(attempt));
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
throw new Error(`SQLite database is locked after ${maxAttempts} attempts while ${context}. Another economy sync/merge may be recovering the database; retry shortly.`, { cause: lastError });
|
|
620
|
-
}
|
|
621
|
-
function openDatabase(dbPath, skipSeed = false) {
|
|
622
|
-
const path = dbPath ?? getDbPath();
|
|
623
|
-
if (path !== ":memory:") {
|
|
624
|
-
const dir = path.substring(0, path.lastIndexOf("/"));
|
|
625
|
-
if (dir && !existsSync(dir))
|
|
626
|
-
mkdirSync(dir, { recursive: true });
|
|
627
|
-
}
|
|
628
|
-
const db = withSqliteBusyRetry(() => {
|
|
629
|
-
const opened = new Database(path);
|
|
630
|
-
try {
|
|
631
|
-
opened.exec("PRAGMA busy_timeout = 10000");
|
|
632
|
-
opened.exec("PRAGMA journal_mode = WAL");
|
|
633
|
-
opened.exec("PRAGMA foreign_keys = ON");
|
|
634
|
-
initSchema(opened);
|
|
635
|
-
return opened;
|
|
636
|
-
} catch (error) {
|
|
637
|
-
try {
|
|
638
|
-
opened.close();
|
|
639
|
-
} catch {}
|
|
640
|
-
throw error;
|
|
641
|
-
}
|
|
642
|
-
}, `opening ${path}`);
|
|
643
|
-
if (!skipSeed) {
|
|
644
|
-
Promise.resolve().then(() => (init_pricing(), exports_pricing)).then(({ ensurePricingSeeded: ensurePricingSeeded2 }) => ensurePricingSeeded2(db)).catch(() => {});
|
|
645
|
-
}
|
|
646
|
-
return db;
|
|
647
|
-
}
|
|
648
|
-
function quoteSqlIdent(identifier) {
|
|
649
|
-
return `"${identifier.replace(/"/g, '""')}"`;
|
|
650
|
-
}
|
|
651
|
-
function hasColumn(db, table, column) {
|
|
652
|
-
const columns = db.prepare(`PRAGMA table_info(${quoteSqlIdent(table)})`).all();
|
|
653
|
-
return columns.some((c) => c.name === column);
|
|
654
|
-
}
|
|
655
|
-
function addColumnIfMissing(db, table, column, definition) {
|
|
656
|
-
if (hasColumn(db, table, column))
|
|
657
|
-
return false;
|
|
658
|
-
try {
|
|
659
|
-
db.exec(`ALTER TABLE ${quoteSqlIdent(table)} ADD COLUMN ${quoteSqlIdent(column)} ${definition}`);
|
|
660
|
-
return true;
|
|
661
|
-
} catch (error) {
|
|
662
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
663
|
-
if (/duplicate column name/i.test(message))
|
|
664
|
-
return true;
|
|
665
|
-
throw error;
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
function initSchema(db) {
|
|
669
|
-
db.exec(`
|
|
670
|
-
CREATE TABLE IF NOT EXISTS cost_centers (
|
|
671
|
-
id TEXT PRIMARY KEY,
|
|
672
|
-
kind TEXT NOT NULL,
|
|
673
|
-
name TEXT NOT NULL,
|
|
674
|
-
repo_path TEXT,
|
|
675
|
-
labels_json TEXT DEFAULT '{}',
|
|
676
|
-
created_at TEXT NOT NULL,
|
|
677
|
-
updated_at TEXT NOT NULL
|
|
678
|
-
);
|
|
679
|
-
|
|
680
|
-
CREATE TABLE IF NOT EXISTS requests (
|
|
681
|
-
id TEXT PRIMARY KEY,
|
|
682
|
-
agent TEXT NOT NULL,
|
|
683
|
-
session_id TEXT NOT NULL,
|
|
684
|
-
model TEXT NOT NULL,
|
|
685
|
-
input_tokens INTEGER DEFAULT 0,
|
|
686
|
-
output_tokens INTEGER DEFAULT 0,
|
|
687
|
-
cache_read_tokens INTEGER DEFAULT 0,
|
|
688
|
-
cache_create_tokens INTEGER DEFAULT 0,
|
|
689
|
-
cache_create_5m_tokens INTEGER DEFAULT 0,
|
|
690
|
-
cache_create_1h_tokens INTEGER DEFAULT 0,
|
|
691
|
-
cost_usd REAL NOT NULL DEFAULT 0,
|
|
692
|
-
duration_ms INTEGER DEFAULT 0,
|
|
693
|
-
timestamp TEXT NOT NULL,
|
|
694
|
-
source_request_id TEXT,
|
|
695
|
-
machine_id TEXT DEFAULT '',
|
|
696
|
-
cost_center_id TEXT,
|
|
697
|
-
account_key TEXT DEFAULT '',
|
|
698
|
-
account_tool TEXT DEFAULT '',
|
|
699
|
-
account_name TEXT DEFAULT '',
|
|
700
|
-
account_email TEXT DEFAULT '',
|
|
701
|
-
account_source TEXT DEFAULT ''
|
|
702
|
-
);
|
|
703
|
-
|
|
704
|
-
CREATE TABLE IF NOT EXISTS sessions (
|
|
705
|
-
id TEXT PRIMARY KEY,
|
|
706
|
-
agent TEXT NOT NULL,
|
|
707
|
-
project_path TEXT DEFAULT '',
|
|
708
|
-
project_name TEXT DEFAULT '',
|
|
709
|
-
started_at TEXT NOT NULL,
|
|
710
|
-
ended_at TEXT,
|
|
711
|
-
total_cost_usd REAL DEFAULT 0,
|
|
712
|
-
total_tokens INTEGER DEFAULT 0,
|
|
713
|
-
request_count INTEGER DEFAULT 0,
|
|
714
|
-
machine_id TEXT DEFAULT '',
|
|
715
|
-
cost_center_id TEXT,
|
|
716
|
-
account_key TEXT DEFAULT '',
|
|
717
|
-
account_tool TEXT DEFAULT '',
|
|
718
|
-
account_name TEXT DEFAULT '',
|
|
719
|
-
account_email TEXT DEFAULT '',
|
|
720
|
-
account_source TEXT DEFAULT ''
|
|
721
|
-
);
|
|
722
|
-
|
|
723
|
-
CREATE TABLE IF NOT EXISTS projects (
|
|
724
|
-
id TEXT PRIMARY KEY,
|
|
725
|
-
path TEXT UNIQUE NOT NULL,
|
|
726
|
-
name TEXT NOT NULL,
|
|
727
|
-
description TEXT,
|
|
728
|
-
tags TEXT DEFAULT '[]',
|
|
729
|
-
created_at TEXT NOT NULL
|
|
730
|
-
);
|
|
731
|
-
|
|
732
|
-
CREATE TABLE IF NOT EXISTS budgets (
|
|
733
|
-
id TEXT PRIMARY KEY,
|
|
734
|
-
project_path TEXT,
|
|
735
|
-
agent TEXT,
|
|
736
|
-
cost_center_id TEXT,
|
|
737
|
-
period TEXT NOT NULL,
|
|
738
|
-
limit_usd REAL NOT NULL,
|
|
739
|
-
alert_at_percent INTEGER DEFAULT 80,
|
|
740
|
-
created_at TEXT NOT NULL,
|
|
741
|
-
updated_at TEXT NOT NULL
|
|
742
|
-
);
|
|
743
|
-
|
|
744
|
-
CREATE TABLE IF NOT EXISTS goals (
|
|
745
|
-
id TEXT PRIMARY KEY,
|
|
746
|
-
period TEXT NOT NULL,
|
|
747
|
-
project_path TEXT,
|
|
748
|
-
agent TEXT,
|
|
749
|
-
limit_usd REAL NOT NULL,
|
|
750
|
-
created_at TEXT NOT NULL,
|
|
751
|
-
updated_at TEXT NOT NULL
|
|
752
|
-
);
|
|
753
|
-
|
|
754
|
-
CREATE TABLE IF NOT EXISTS ingest_state (
|
|
755
|
-
source TEXT NOT NULL,
|
|
756
|
-
key TEXT NOT NULL,
|
|
757
|
-
value TEXT NOT NULL,
|
|
758
|
-
PRIMARY KEY (source, key)
|
|
759
|
-
);
|
|
760
|
-
|
|
761
|
-
CREATE INDEX IF NOT EXISTS idx_requests_session ON requests(session_id);
|
|
762
|
-
CREATE INDEX IF NOT EXISTS idx_requests_timestamp ON requests(timestamp);
|
|
763
|
-
CREATE INDEX IF NOT EXISTS idx_requests_agent ON requests(agent);
|
|
764
|
-
CREATE INDEX IF NOT EXISTS idx_sessions_agent ON sessions(agent);
|
|
765
|
-
CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_path);
|
|
766
|
-
CREATE INDEX IF NOT EXISTS idx_sessions_started ON sessions(started_at);
|
|
767
|
-
CREATE INDEX IF NOT EXISTS idx_cost_centers_kind ON cost_centers(kind);
|
|
768
|
-
|
|
769
|
-
CREATE TABLE IF NOT EXISTS model_pricing (
|
|
770
|
-
model TEXT PRIMARY KEY,
|
|
771
|
-
input_per_1m REAL NOT NULL DEFAULT 0,
|
|
772
|
-
output_per_1m REAL NOT NULL DEFAULT 0,
|
|
773
|
-
cache_read_per_1m REAL NOT NULL DEFAULT 0,
|
|
774
|
-
cache_write_per_1m REAL NOT NULL DEFAULT 0,
|
|
775
|
-
cache_write_1h_per_1m REAL NOT NULL DEFAULT 0,
|
|
776
|
-
cache_storage_per_1m_hour REAL NOT NULL DEFAULT 0,
|
|
777
|
-
updated_at TEXT NOT NULL
|
|
778
|
-
);
|
|
779
|
-
|
|
780
|
-
CREATE TABLE IF NOT EXISTS feedback (
|
|
781
|
-
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
|
782
|
-
message TEXT NOT NULL,
|
|
783
|
-
email TEXT,
|
|
784
|
-
category TEXT DEFAULT 'general',
|
|
785
|
-
version TEXT,
|
|
786
|
-
machine_id TEXT,
|
|
787
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
788
|
-
);
|
|
789
|
-
|
|
790
|
-
CREATE TABLE IF NOT EXISTS billing_daily (
|
|
791
|
-
date TEXT NOT NULL,
|
|
792
|
-
provider TEXT NOT NULL,
|
|
793
|
-
description TEXT DEFAULT '',
|
|
794
|
-
cost_usd REAL NOT NULL DEFAULT 0,
|
|
795
|
-
updated_at TEXT NOT NULL,
|
|
796
|
-
PRIMARY KEY (date, provider, description)
|
|
797
|
-
);
|
|
798
|
-
|
|
799
|
-
CREATE INDEX IF NOT EXISTS idx_billing_date ON billing_daily(date);
|
|
800
|
-
CREATE INDEX IF NOT EXISTS idx_billing_provider ON billing_daily(provider);
|
|
801
|
-
|
|
802
|
-
CREATE TABLE IF NOT EXISTS subscriptions (
|
|
803
|
-
id TEXT PRIMARY KEY,
|
|
804
|
-
agent TEXT,
|
|
805
|
-
provider TEXT NOT NULL,
|
|
806
|
-
plan TEXT NOT NULL,
|
|
807
|
-
monthly_fee_usd REAL NOT NULL DEFAULT 0,
|
|
808
|
-
included_usage_usd REAL NOT NULL DEFAULT 0,
|
|
809
|
-
billing_cycle_start TEXT,
|
|
810
|
-
reset_policy TEXT DEFAULT 'monthly',
|
|
811
|
-
active INTEGER NOT NULL DEFAULT 1,
|
|
812
|
-
created_at TEXT NOT NULL,
|
|
813
|
-
updated_at TEXT NOT NULL
|
|
814
|
-
);
|
|
815
|
-
|
|
816
|
-
CREATE TABLE IF NOT EXISTS usage_snapshots (
|
|
817
|
-
id TEXT PRIMARY KEY,
|
|
818
|
-
agent TEXT NOT NULL,
|
|
819
|
-
date TEXT NOT NULL,
|
|
820
|
-
metric TEXT NOT NULL,
|
|
821
|
-
value REAL NOT NULL DEFAULT 0,
|
|
822
|
-
unit TEXT DEFAULT '',
|
|
823
|
-
machine_id TEXT DEFAULT '',
|
|
824
|
-
updated_at TEXT NOT NULL
|
|
825
|
-
);
|
|
826
|
-
|
|
827
|
-
CREATE TABLE IF NOT EXISTS savings_daily (
|
|
828
|
-
date TEXT NOT NULL,
|
|
829
|
-
agent TEXT DEFAULT '',
|
|
830
|
-
api_equivalent_usd REAL NOT NULL DEFAULT 0,
|
|
831
|
-
subscription_fee_usd REAL NOT NULL DEFAULT 0,
|
|
832
|
-
included_consumed_usd REAL NOT NULL DEFAULT 0,
|
|
833
|
-
on_demand_usd REAL NOT NULL DEFAULT 0,
|
|
834
|
-
saved_usd REAL NOT NULL DEFAULT 0,
|
|
835
|
-
updated_at TEXT NOT NULL,
|
|
836
|
-
PRIMARY KEY (date, agent)
|
|
837
|
-
);
|
|
838
|
-
|
|
839
|
-
CREATE TABLE IF NOT EXISTS machines (
|
|
840
|
-
machine_id TEXT PRIMARY KEY,
|
|
841
|
-
hostname TEXT NOT NULL,
|
|
842
|
-
last_seen_at TEXT,
|
|
843
|
-
last_push_at TEXT,
|
|
844
|
-
last_pull_at TEXT,
|
|
845
|
-
economy_version TEXT,
|
|
846
|
-
updated_at TEXT NOT NULL
|
|
847
|
-
);
|
|
848
|
-
|
|
849
|
-
CREATE INDEX IF NOT EXISTS idx_usage_agent_date ON usage_snapshots(agent, date);
|
|
850
|
-
CREATE INDEX IF NOT EXISTS idx_savings_date ON savings_daily(date);
|
|
851
|
-
`);
|
|
852
|
-
addColumnIfMissing(db, "requests", "machine_id", `TEXT DEFAULT ''`);
|
|
853
|
-
addColumnIfMissing(db, "sessions", "machine_id", `TEXT DEFAULT ''`);
|
|
854
|
-
if (addColumnIfMissing(db, "cost_centers", "updated_at", `TEXT DEFAULT ''`)) {
|
|
855
|
-
db.exec(`UPDATE cost_centers SET updated_at = created_at WHERE updated_at = '' OR updated_at IS NULL`);
|
|
856
|
-
}
|
|
857
|
-
addColumnIfMissing(db, "requests", "cost_center_id", `TEXT`);
|
|
858
|
-
addColumnIfMissing(db, "sessions", "cost_center_id", `TEXT`);
|
|
859
|
-
addColumnIfMissing(db, "budgets", "cost_center_id", `TEXT`);
|
|
860
|
-
if (addColumnIfMissing(db, "requests", "cache_create_5m_tokens", "INTEGER DEFAULT 0")) {
|
|
861
|
-
db.exec(`UPDATE requests SET cache_create_5m_tokens = cache_create_tokens WHERE cache_create_5m_tokens = 0`);
|
|
862
|
-
}
|
|
863
|
-
addColumnIfMissing(db, "requests", "cache_create_1h_tokens", "INTEGER DEFAULT 0");
|
|
864
|
-
addColumnIfMissing(db, "requests", "cost_basis", `TEXT DEFAULT 'estimated'`);
|
|
865
|
-
addColumnIfMissing(db, "requests", "attribution_tag", `TEXT DEFAULT ''`);
|
|
866
|
-
if (addColumnIfMissing(db, "requests", "updated_at", `TEXT DEFAULT ''`)) {
|
|
867
|
-
db.exec(`UPDATE requests SET updated_at = timestamp WHERE updated_at = '' OR updated_at IS NULL`);
|
|
868
|
-
}
|
|
869
|
-
addColumnIfMissing(db, "requests", "synced_at", `TEXT DEFAULT ''`);
|
|
870
|
-
for (const column of ["account_key", "account_tool", "account_name", "account_email", "account_source"]) {
|
|
871
|
-
addColumnIfMissing(db, "requests", column, `TEXT DEFAULT ''`);
|
|
872
|
-
}
|
|
873
|
-
addColumnIfMissing(db, "sessions", "attribution_tag", `TEXT DEFAULT ''`);
|
|
874
|
-
if (addColumnIfMissing(db, "sessions", "updated_at", `TEXT DEFAULT ''`)) {
|
|
875
|
-
db.exec(`UPDATE sessions SET updated_at = started_at WHERE updated_at = '' OR updated_at IS NULL`);
|
|
876
|
-
}
|
|
877
|
-
addColumnIfMissing(db, "sessions", "synced_at", `TEXT DEFAULT ''`);
|
|
878
|
-
for (const column of ["account_key", "account_tool", "account_name", "account_email", "account_source"]) {
|
|
879
|
-
addColumnIfMissing(db, "sessions", column, `TEXT DEFAULT ''`);
|
|
880
|
-
}
|
|
881
|
-
addColumnIfMissing(db, "model_pricing", "cache_write_1h_per_1m", "REAL NOT NULL DEFAULT 0");
|
|
882
|
-
addColumnIfMissing(db, "model_pricing", "cache_storage_per_1m_hour", "REAL NOT NULL DEFAULT 0");
|
|
883
|
-
db.exec(`
|
|
884
|
-
CREATE INDEX IF NOT EXISTS idx_requests_machine ON requests(machine_id);
|
|
885
|
-
CREATE INDEX IF NOT EXISTS idx_sessions_machine ON sessions(machine_id);
|
|
886
|
-
CREATE INDEX IF NOT EXISTS idx_requests_account ON requests(account_key);
|
|
887
|
-
CREATE INDEX IF NOT EXISTS idx_sessions_account ON sessions(account_key);
|
|
888
|
-
CREATE INDEX IF NOT EXISTS idx_requests_cost_center ON requests(cost_center_id);
|
|
889
|
-
CREATE INDEX IF NOT EXISTS idx_sessions_cost_center ON sessions(cost_center_id);
|
|
890
|
-
CREATE INDEX IF NOT EXISTS idx_budgets_cost_center ON budgets(cost_center_id);
|
|
891
|
-
CREATE INDEX IF NOT EXISTS idx_cost_centers_kind ON cost_centers(kind);
|
|
892
|
-
`);
|
|
893
|
-
}
|
|
894
|
-
function periodWhere(period) {
|
|
895
|
-
switch (period) {
|
|
896
|
-
case "today":
|
|
897
|
-
return `DATE(timestamp) = DATE('now')`;
|
|
898
|
-
case "yesterday":
|
|
899
|
-
return `DATE(timestamp) = DATE('now', '-1 day')`;
|
|
900
|
-
case "week":
|
|
901
|
-
return `timestamp >= DATE('now', 'weekday 0', '-7 days')`;
|
|
902
|
-
case "month":
|
|
903
|
-
return `timestamp >= DATE('now', 'start of month')`;
|
|
904
|
-
case "year":
|
|
905
|
-
return `timestamp >= DATE('now', 'start of year')`;
|
|
906
|
-
case "all":
|
|
907
|
-
return "1=1";
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
function sessionPeriodWhere(period) {
|
|
911
|
-
switch (period) {
|
|
912
|
-
case "today":
|
|
913
|
-
return `DATE(started_at) = DATE('now')`;
|
|
914
|
-
case "yesterday":
|
|
915
|
-
return `DATE(started_at) = DATE('now', '-1 day')`;
|
|
916
|
-
case "week":
|
|
917
|
-
return `started_at >= DATE('now', 'weekday 0', '-7 days')`;
|
|
918
|
-
case "month":
|
|
919
|
-
return `started_at >= DATE('now', 'start of month')`;
|
|
920
|
-
case "year":
|
|
921
|
-
return `started_at >= DATE('now', 'start of year')`;
|
|
922
|
-
case "all":
|
|
923
|
-
return "1=1";
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
function requestPeriodWhere(period) {
|
|
927
|
-
switch (period) {
|
|
928
|
-
case "today":
|
|
929
|
-
return `DATE(timestamp) = DATE('now')`;
|
|
930
|
-
case "yesterday":
|
|
931
|
-
return `DATE(timestamp) = DATE('now', '-1 day')`;
|
|
932
|
-
case "week":
|
|
933
|
-
return `timestamp >= DATE('now', 'weekday 0', '-7 days')`;
|
|
934
|
-
case "month":
|
|
935
|
-
return `timestamp >= DATE('now', 'start of month')`;
|
|
936
|
-
case "year":
|
|
937
|
-
return `timestamp >= DATE('now', 'start of year')`;
|
|
938
|
-
case "all":
|
|
939
|
-
return "1=1";
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
function querySessions(db, filter = {}) {
|
|
943
|
-
const conditions = [];
|
|
944
|
-
const params = [];
|
|
945
|
-
if (filter.agent) {
|
|
946
|
-
conditions.push("agent = ?");
|
|
947
|
-
params.push(filter.agent);
|
|
948
|
-
}
|
|
949
|
-
if (filter.project) {
|
|
950
|
-
conditions.push("project_path LIKE ?");
|
|
951
|
-
params.push(`%${filter.project}%`);
|
|
952
|
-
}
|
|
953
|
-
if (filter.account) {
|
|
954
|
-
const q = `%${filter.account}%`;
|
|
955
|
-
conditions.push("(account_key LIKE ? OR account_name LIKE ? OR account_email LIKE ?)");
|
|
956
|
-
params.push(q, q, q);
|
|
957
|
-
}
|
|
958
|
-
if (filter.since) {
|
|
959
|
-
conditions.push("started_at >= ?");
|
|
960
|
-
params.push(filter.since);
|
|
961
|
-
}
|
|
962
|
-
if (filter.machine) {
|
|
963
|
-
conditions.push("machine_id = ?");
|
|
964
|
-
params.push(filter.machine);
|
|
965
|
-
}
|
|
966
|
-
if (filter.search) {
|
|
967
|
-
const q = `%${filter.search}%`;
|
|
968
|
-
conditions.push("(project_name LIKE ? OR agent LIKE ? OR id LIKE ?)");
|
|
969
|
-
params.push(q, q, `${filter.search}%`);
|
|
970
|
-
}
|
|
971
|
-
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
972
|
-
const limit = filter.limit ?? 50;
|
|
973
|
-
const offset = filter.offset ?? 0;
|
|
974
|
-
return db.prepare(`
|
|
975
|
-
SELECT * FROM sessions ${where} ORDER BY started_at DESC LIMIT ? OFFSET ?
|
|
976
|
-
`).all(...params, limit, offset);
|
|
977
|
-
}
|
|
978
|
-
function queryTopSessions(db, n = 10, agent, since) {
|
|
979
|
-
const conditions = [];
|
|
980
|
-
const params = [];
|
|
981
|
-
if (agent) {
|
|
982
|
-
conditions.push("agent = ?");
|
|
983
|
-
params.push(agent);
|
|
984
|
-
}
|
|
985
|
-
if (since) {
|
|
986
|
-
conditions.push("started_at >= ?");
|
|
987
|
-
params.push(since);
|
|
988
|
-
}
|
|
989
|
-
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
990
|
-
return db.prepare(`SELECT * FROM sessions ${where} ORDER BY total_cost_usd DESC LIMIT ?`).all(...params, n);
|
|
991
|
-
}
|
|
992
|
-
function querySummary(db, period, machine, allMachines = false, agent) {
|
|
993
|
-
const rWhere = periodWhere(period);
|
|
994
|
-
const sWhere = sessionPeriodWhere(period);
|
|
995
|
-
const machineClause = !allMachines && machine ? " AND machine_id = ?" : "";
|
|
996
|
-
const agentClause = agent ? " AND agent = ?" : "";
|
|
997
|
-
const params = [];
|
|
998
|
-
if (!allMachines && machine)
|
|
999
|
-
params.push(machine);
|
|
1000
|
-
if (agent)
|
|
1001
|
-
params.push(agent);
|
|
1002
|
-
const r = db.prepare(`
|
|
1003
|
-
SELECT COALESCE(SUM(cost_usd), 0) as total_usd,
|
|
1004
|
-
COUNT(*) as requests,
|
|
1005
|
-
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as tokens
|
|
1006
|
-
FROM requests WHERE ${rWhere}${machineClause}${agentClause}
|
|
1007
|
-
`).get(...params);
|
|
1008
|
-
const codexTotals = db.prepare(`
|
|
1009
|
-
SELECT COALESCE(SUM(total_cost_usd), 0) as cost_usd,
|
|
1010
|
-
COALESCE(SUM(total_tokens), 0) as tokens,
|
|
1011
|
-
COALESCE(SUM(request_count), 0) as requests,
|
|
1012
|
-
COUNT(*) as sessions
|
|
1013
|
-
FROM sessions
|
|
1014
|
-
WHERE ${sWhere}${machineClause}${agentClause}
|
|
1015
|
-
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
1016
|
-
`).get(...params);
|
|
1017
|
-
const requestSessionCount = db.prepare(`
|
|
1018
|
-
SELECT COUNT(DISTINCT session_id) as sessions
|
|
1019
|
-
FROM requests
|
|
1020
|
-
WHERE ${rWhere}${machineClause}${agentClause}
|
|
1021
|
-
`).get(...params);
|
|
1022
|
-
return {
|
|
1023
|
-
total_usd: r.total_usd + codexTotals.cost_usd,
|
|
1024
|
-
requests: r.requests + codexTotals.requests,
|
|
1025
|
-
tokens: r.tokens + codexTotals.tokens,
|
|
1026
|
-
sessions: requestSessionCount.sessions + codexTotals.sessions,
|
|
1027
|
-
period
|
|
1028
|
-
};
|
|
1029
|
-
}
|
|
1030
|
-
function queryModelBreakdown(db) {
|
|
1031
|
-
return db.prepare(`
|
|
1032
|
-
SELECT model, agent,
|
|
1033
|
-
COUNT(*) as requests,
|
|
1034
|
-
COALESCE(SUM(input_tokens), 0) as input_tokens,
|
|
1035
|
-
COALESCE(SUM(output_tokens), 0) as output_tokens,
|
|
1036
|
-
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens,
|
|
1037
|
-
COALESCE(SUM(cost_usd), 0) as cost_usd
|
|
1038
|
-
FROM requests GROUP BY model, agent ORDER BY cost_usd DESC
|
|
1039
|
-
`).all();
|
|
1040
|
-
}
|
|
1041
|
-
function queryAgentBreakdown(db, period = "all", machine) {
|
|
1042
|
-
const requestWhere = requestPeriodWhere(period);
|
|
1043
|
-
const machineClause = machine ? " AND machine_id = ?" : "";
|
|
1044
|
-
const machineParams = machine ? [machine] : [];
|
|
1045
|
-
const groups = new Map;
|
|
1046
|
-
const requestRows = db.prepare(`
|
|
1047
|
-
SELECT agent,
|
|
1048
|
-
COUNT(DISTINCT session_id) as sessions,
|
|
1049
|
-
COUNT(*) as requests,
|
|
1050
|
-
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens,
|
|
1051
|
-
COALESCE(SUM(cost_usd), 0) as api_equivalent_usd,
|
|
1052
|
-
COALESCE(SUM(CASE WHEN cost_basis = 'metered_api' THEN cost_usd ELSE 0 END), 0) as metered_api_usd,
|
|
1053
|
-
COALESCE(SUM(CASE WHEN cost_basis = 'subscription_included' THEN cost_usd ELSE 0 END), 0) as subscription_included_usd,
|
|
1054
|
-
COALESCE(SUM(CASE WHEN COALESCE(cost_basis, 'estimated') = 'estimated' THEN cost_usd ELSE 0 END), 0) as estimated_usd,
|
|
1055
|
-
COALESCE(SUM(CASE WHEN cost_basis = 'unknown' THEN cost_usd ELSE 0 END), 0) as unknown_usd,
|
|
1056
|
-
COALESCE(SUM(CASE WHEN cost_basis = 'metered_api' THEN cost_usd ELSE 0 END), 0) as billable_usd,
|
|
1057
|
-
COALESCE(SUM(cost_usd), 0) as cost_usd,
|
|
1058
|
-
MAX(timestamp) as last_active
|
|
1059
|
-
FROM requests
|
|
1060
|
-
WHERE ${requestWhere}${machineClause}
|
|
1061
|
-
GROUP BY agent
|
|
1062
|
-
ORDER BY api_equivalent_usd DESC
|
|
1063
|
-
`).all(...machineParams);
|
|
1064
|
-
for (const row of requestRows) {
|
|
1065
|
-
groups.set(row.agent, row);
|
|
1066
|
-
}
|
|
1067
|
-
const sessionWhere = sessionPeriodWhere(period);
|
|
1068
|
-
const sessionOnlyRows = db.prepare(`
|
|
1069
|
-
SELECT agent,
|
|
1070
|
-
COUNT(*) as sessions,
|
|
1071
|
-
COALESCE(SUM(request_count), 0) as requests,
|
|
1072
|
-
COALESCE(SUM(total_tokens), 0) as total_tokens,
|
|
1073
|
-
COALESCE(SUM(total_cost_usd), 0) as cost_usd,
|
|
1074
|
-
MAX(started_at) as last_active
|
|
1075
|
-
FROM sessions
|
|
1076
|
-
WHERE ${sessionWhere}${machineClause}
|
|
1077
|
-
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
1078
|
-
GROUP BY agent
|
|
1079
|
-
`).all(...machineParams);
|
|
1080
|
-
for (const row of sessionOnlyRows) {
|
|
1081
|
-
const existing = groups.get(row.agent) ?? {
|
|
1082
|
-
agent: row.agent,
|
|
1083
|
-
sessions: 0,
|
|
1084
|
-
requests: 0,
|
|
1085
|
-
total_tokens: 0,
|
|
1086
|
-
api_equivalent_usd: 0,
|
|
1087
|
-
billable_usd: 0,
|
|
1088
|
-
metered_api_usd: 0,
|
|
1089
|
-
subscription_included_usd: 0,
|
|
1090
|
-
estimated_usd: 0,
|
|
1091
|
-
unknown_usd: 0,
|
|
1092
|
-
cost_usd: 0,
|
|
1093
|
-
last_active: ""
|
|
1094
|
-
};
|
|
1095
|
-
existing.sessions += row.sessions;
|
|
1096
|
-
existing.requests += row.requests;
|
|
1097
|
-
existing.total_tokens += row.total_tokens;
|
|
1098
|
-
existing.api_equivalent_usd += row.cost_usd;
|
|
1099
|
-
existing.estimated_usd += row.cost_usd;
|
|
1100
|
-
existing.cost_usd += row.cost_usd;
|
|
1101
|
-
if (!existing.last_active || row.last_active > existing.last_active)
|
|
1102
|
-
existing.last_active = row.last_active;
|
|
1103
|
-
groups.set(row.agent, existing);
|
|
1104
|
-
}
|
|
1105
|
-
return [...groups.values()].sort((a, b) => b.api_equivalent_usd - a.api_equivalent_usd);
|
|
1106
|
-
}
|
|
1107
|
-
function pathProjectLabel(projectPath) {
|
|
1108
|
-
if (!projectPath)
|
|
1109
|
-
return "";
|
|
1110
|
-
const segments = projectPath.split("/").filter(Boolean);
|
|
1111
|
-
const projectPrefix = /^(open|skill|hook|service|connect|platform|agent|tool|iapp|project|scaffold|capp)-/;
|
|
1112
|
-
for (const seg of segments) {
|
|
1113
|
-
if (projectPrefix.test(seg))
|
|
1114
|
-
return seg;
|
|
1115
|
-
}
|
|
1116
|
-
const generic = new Set([
|
|
1117
|
-
"web",
|
|
1118
|
-
"app",
|
|
1119
|
-
"apps",
|
|
1120
|
-
"packages",
|
|
1121
|
-
"src",
|
|
1122
|
-
"lib",
|
|
1123
|
-
"server",
|
|
1124
|
-
"client",
|
|
1125
|
-
"api",
|
|
1126
|
-
"frontend",
|
|
1127
|
-
"backend",
|
|
1128
|
-
"home",
|
|
1129
|
-
"users",
|
|
1130
|
-
"workspace",
|
|
1131
|
-
"workspaces",
|
|
1132
|
-
"hasna"
|
|
1133
|
-
]);
|
|
1134
|
-
for (let i = segments.length - 1;i >= 0; i--) {
|
|
1135
|
-
if (!generic.has(segments[i].toLowerCase()))
|
|
1136
|
-
return segments[i];
|
|
1137
|
-
}
|
|
1138
|
-
return null;
|
|
1139
|
-
}
|
|
1140
|
-
function isRepoLikeLabel(label) {
|
|
1141
|
-
return /^(open|skill|hook|service|connect|platform|agent|tool|iapp|project|scaffold|capp)-/.test(label) || label.includes("-");
|
|
1142
|
-
}
|
|
1143
|
-
function labelForPath(projectPath, projectName) {
|
|
1144
|
-
const pathLabel = pathProjectLabel(projectPath);
|
|
1145
|
-
if (pathLabel && (!projectName || projectName.trim() === "" || isRepoLikeLabel(pathLabel)))
|
|
1146
|
-
return pathLabel;
|
|
1147
|
-
if (projectName && projectName.trim() !== "")
|
|
1148
|
-
return projectName;
|
|
1149
|
-
if (pathLabel)
|
|
1150
|
-
return pathLabel;
|
|
1151
|
-
return projectPath;
|
|
1152
|
-
}
|
|
1153
|
-
function groupKeyForPath(projectPath, projectName) {
|
|
1154
|
-
return labelForPath(projectPath, projectName).trim().toLowerCase();
|
|
1155
|
-
}
|
|
1156
|
-
function queryProjectBreakdown(db, period = "all", machine) {
|
|
1157
|
-
const requestWhere = requestPeriodWhere(period);
|
|
1158
|
-
const sessionWhere = sessionPeriodWhere(period);
|
|
1159
|
-
const sessionMachineClause = machine ? " AND (machine_id = ? OR id IN (SELECT DISTINCT session_id FROM requests WHERE machine_id = ?))" : "";
|
|
1160
|
-
const requestMachineClause = machine ? " AND machine_id = ?" : "";
|
|
1161
|
-
const sessionMachineParams = machine ? [machine, machine] : [];
|
|
1162
|
-
const requestMachineParams = machine ? [machine] : [];
|
|
1163
|
-
const sessionOnlyMachineClause = machine ? " AND machine_id = ?" : "";
|
|
1164
|
-
const sessionOnlyMachineParams = machine ? [machine] : [];
|
|
1165
|
-
const sessions = db.prepare(`
|
|
1166
|
-
SELECT id, project_path, project_name, total_cost_usd, started_at
|
|
1167
|
-
FROM sessions
|
|
1168
|
-
WHERE (project_path != '' OR project_name != '')${sessionMachineClause}
|
|
1169
|
-
`).all(...sessionMachineParams);
|
|
1170
|
-
const groups = new Map;
|
|
1171
|
-
for (const s of sessions) {
|
|
1172
|
-
const label = labelForPath(s.project_path, s.project_name);
|
|
1173
|
-
if (!label)
|
|
1174
|
-
continue;
|
|
1175
|
-
const key = groupKeyForPath(s.project_path, s.project_name);
|
|
1176
|
-
const g = groups.get(key) ?? { label, sessionIds: [], samplePath: s.project_path };
|
|
1177
|
-
g.sessionIds.push(s.id);
|
|
1178
|
-
if (!g.samplePath)
|
|
1179
|
-
g.samplePath = s.project_path;
|
|
1180
|
-
groups.set(key, g);
|
|
1181
|
-
}
|
|
1182
|
-
const result = [];
|
|
1183
|
-
for (const g of groups.values()) {
|
|
1184
|
-
const placeholders = g.sessionIds.map(() => "?").join(",");
|
|
1185
|
-
const reqStats = placeholders.length ? db.prepare(`
|
|
1186
|
-
SELECT
|
|
1187
|
-
COUNT(DISTINCT session_id) as sessions,
|
|
1188
|
-
COUNT(*) as requests,
|
|
1189
|
-
COALESCE(SUM(cost_usd), 0) as cost_usd,
|
|
1190
|
-
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens,
|
|
1191
|
-
MAX(timestamp) as last_active
|
|
1192
|
-
FROM requests
|
|
1193
|
-
WHERE session_id IN (${placeholders})
|
|
1194
|
-
AND ${requestWhere}
|
|
1195
|
-
${requestMachineClause}
|
|
1196
|
-
`).get(...g.sessionIds, ...requestMachineParams) : { sessions: 0, requests: 0, cost_usd: 0, total_tokens: 0, last_active: null };
|
|
1197
|
-
const sessionOnlyStats = placeholders.length ? db.prepare(`
|
|
1198
|
-
SELECT
|
|
1199
|
-
COUNT(*) as sessions,
|
|
1200
|
-
COALESCE(SUM(request_count), 0) as requests,
|
|
1201
|
-
COALESCE(SUM(total_tokens), 0) as total_tokens,
|
|
1202
|
-
COALESCE(SUM(total_cost_usd), 0) as cost_usd,
|
|
1203
|
-
MAX(started_at) as last_active
|
|
1204
|
-
FROM sessions
|
|
1205
|
-
WHERE id IN (${placeholders})
|
|
1206
|
-
AND ${sessionWhere}
|
|
1207
|
-
${sessionOnlyMachineClause}
|
|
1208
|
-
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
1209
|
-
`).get(...g.sessionIds, ...sessionOnlyMachineParams) : { sessions: 0, requests: 0, total_tokens: 0, cost_usd: 0, last_active: null };
|
|
1210
|
-
const totalSessions = reqStats.sessions + sessionOnlyStats.sessions;
|
|
1211
|
-
if (totalSessions === 0)
|
|
1212
|
-
continue;
|
|
1213
|
-
const lastActive = [reqStats.last_active, sessionOnlyStats.last_active].filter(Boolean).sort().at(-1) ?? "";
|
|
1214
|
-
result.push({
|
|
1215
|
-
project_path: g.samplePath,
|
|
1216
|
-
project_name: g.label,
|
|
1217
|
-
sessions: totalSessions,
|
|
1218
|
-
requests: reqStats.requests + sessionOnlyStats.requests,
|
|
1219
|
-
total_tokens: reqStats.total_tokens + sessionOnlyStats.total_tokens,
|
|
1220
|
-
cost_usd: reqStats.cost_usd + sessionOnlyStats.cost_usd,
|
|
1221
|
-
last_active: lastActive
|
|
1222
|
-
});
|
|
1223
|
-
}
|
|
1224
|
-
result.sort((a, b) => b.cost_usd - a.cost_usd);
|
|
1225
|
-
return result;
|
|
1226
|
-
}
|
|
1227
|
-
function normalizeAccountEmail(email) {
|
|
1228
|
-
return (email ?? "").trim().toLowerCase();
|
|
1229
|
-
}
|
|
1230
|
-
function accountIdentityKey(agent, accountKey, accountName, accountEmail) {
|
|
1231
|
-
const identityAgent = (agent || "").trim();
|
|
1232
|
-
const normalizedEmail = normalizeAccountEmail(accountEmail);
|
|
1233
|
-
if (identityAgent && normalizedEmail)
|
|
1234
|
-
return `${identityAgent}:${normalizedEmail}`;
|
|
1235
|
-
if (identityAgent && accountName)
|
|
1236
|
-
return `${identityAgent}:${accountName}`;
|
|
1237
|
-
if (accountKey)
|
|
1238
|
-
return accountKey;
|
|
1239
|
-
return identityAgent ? `${identityAgent}:unknown` : "";
|
|
1240
|
-
}
|
|
1241
|
-
function addAccountBreakdownRow(groups, row, sessionOnly) {
|
|
1242
|
-
const agent = row.agent || row.account_tool;
|
|
1243
|
-
const email = normalizeAccountEmail(row.account_email);
|
|
1244
|
-
const accountName = row.account_name || email || row.account_key;
|
|
1245
|
-
const key = accountIdentityKey(agent, row.account_key, accountName, email);
|
|
1246
|
-
if (!key)
|
|
1247
|
-
return;
|
|
1248
|
-
const group = groups.get(key) ?? {
|
|
1249
|
-
account_key: key,
|
|
1250
|
-
account_tool: agent,
|
|
1251
|
-
account_name: accountName,
|
|
1252
|
-
account_email: email || null,
|
|
1253
|
-
account_source: row.account_source || "unknown",
|
|
1254
|
-
sessionIds: new Set,
|
|
1255
|
-
requests: 0,
|
|
1256
|
-
total_tokens: 0,
|
|
1257
|
-
api_equivalent_usd: 0,
|
|
1258
|
-
metered_api_usd: 0,
|
|
1259
|
-
subscription_included_usd: 0,
|
|
1260
|
-
estimated_usd: 0,
|
|
1261
|
-
unknown_usd: 0,
|
|
1262
|
-
last_active: ""
|
|
1263
|
-
};
|
|
1264
|
-
if (!group.account_email && email)
|
|
1265
|
-
group.account_email = email;
|
|
1266
|
-
if (!group.account_name && accountName)
|
|
1267
|
-
group.account_name = accountName;
|
|
1268
|
-
if ((!group.account_source || group.account_source === "unknown") && row.account_source && row.account_source !== "unknown") {
|
|
1269
|
-
group.account_source = row.account_source;
|
|
1270
|
-
}
|
|
1271
|
-
if (row.session_id)
|
|
1272
|
-
group.sessionIds.add(row.session_id);
|
|
1273
|
-
group.requests += row.requests;
|
|
1274
|
-
group.total_tokens += row.total_tokens;
|
|
1275
|
-
group.api_equivalent_usd += row.cost_usd;
|
|
1276
|
-
if (sessionOnly) {
|
|
1277
|
-
group.estimated_usd += row.cost_usd;
|
|
1278
|
-
} else if (row.cost_basis === "metered_api") {
|
|
1279
|
-
group.metered_api_usd += row.cost_usd;
|
|
1280
|
-
} else if (row.cost_basis === "subscription_included") {
|
|
1281
|
-
group.subscription_included_usd += row.cost_usd;
|
|
1282
|
-
} else if (row.cost_basis === "unknown") {
|
|
1283
|
-
group.unknown_usd += row.cost_usd;
|
|
1284
|
-
} else {
|
|
1285
|
-
group.estimated_usd += row.cost_usd;
|
|
1286
|
-
}
|
|
1287
|
-
if (!group.last_active || row.last_active > group.last_active)
|
|
1288
|
-
group.last_active = row.last_active;
|
|
1289
|
-
groups.set(key, group);
|
|
1290
|
-
}
|
|
1291
|
-
function queryAccountBreakdown(db, period = "all", machine) {
|
|
1292
|
-
const requestWhere = requestPeriodWhere(period);
|
|
1293
|
-
const sessionWhere = sessionPeriodWhere(period);
|
|
1294
|
-
const requestMachineClause = machine ? " AND r.machine_id = ?" : "";
|
|
1295
|
-
const sessionMachineClause = machine ? " AND s.machine_id = ?" : "";
|
|
1296
|
-
const requestMachineParams = machine ? [machine] : [];
|
|
1297
|
-
const sessionMachineParams = machine ? [machine] : [];
|
|
1298
|
-
const groups = new Map;
|
|
1299
|
-
const requestRows = db.prepare(`
|
|
1300
|
-
SELECT
|
|
1301
|
-
r.session_id as session_id,
|
|
1302
|
-
COALESCE(NULLIF(r.agent, ''), NULLIF(s.agent, ''), '') as agent,
|
|
1303
|
-
COALESCE(NULLIF(r.account_key, ''), NULLIF(s.account_key, ''), '') as account_key,
|
|
1304
|
-
COALESCE(NULLIF(r.account_tool, ''), NULLIF(s.account_tool, ''), '') as account_tool,
|
|
1305
|
-
COALESCE(NULLIF(r.account_name, ''), NULLIF(s.account_name, ''), '') as account_name,
|
|
1306
|
-
COALESCE(NULLIF(r.account_email, ''), NULLIF(s.account_email, ''), '') as account_email,
|
|
1307
|
-
COALESCE(NULLIF(r.account_source, ''), NULLIF(s.account_source, ''), 'unknown') as account_source,
|
|
1308
|
-
1 as requests,
|
|
1309
|
-
COALESCE(r.input_tokens + r.output_tokens + r.cache_read_tokens + r.cache_create_tokens, 0) as total_tokens,
|
|
1310
|
-
COALESCE(r.cost_usd, 0) as cost_usd,
|
|
1311
|
-
COALESCE(NULLIF(r.cost_basis, ''), 'estimated') as cost_basis,
|
|
1312
|
-
r.timestamp as last_active
|
|
1313
|
-
FROM requests r
|
|
1314
|
-
LEFT JOIN sessions s ON s.id = r.session_id
|
|
1315
|
-
WHERE ${requestWhere}${requestMachineClause}
|
|
1316
|
-
AND (
|
|
1317
|
-
COALESCE(NULLIF(r.account_key, ''), NULLIF(s.account_key, ''), '') != ''
|
|
1318
|
-
OR COALESCE(NULLIF(r.account_tool, ''), NULLIF(s.account_tool, ''), '') != ''
|
|
1319
|
-
OR COALESCE(NULLIF(r.account_name, ''), NULLIF(s.account_name, ''), '') != ''
|
|
1320
|
-
OR COALESCE(NULLIF(r.account_email, ''), NULLIF(s.account_email, ''), '') != ''
|
|
1321
|
-
)
|
|
1322
|
-
`).all(...requestMachineParams);
|
|
1323
|
-
for (const row of requestRows)
|
|
1324
|
-
addAccountBreakdownRow(groups, row, false);
|
|
1325
|
-
const sessionOnlyRows = db.prepare(`
|
|
1326
|
-
SELECT
|
|
1327
|
-
s.id as session_id,
|
|
1328
|
-
s.agent as agent,
|
|
1329
|
-
s.account_key as account_key,
|
|
1330
|
-
s.account_tool as account_tool,
|
|
1331
|
-
s.account_name as account_name,
|
|
1332
|
-
s.account_email as account_email,
|
|
1333
|
-
COALESCE(NULLIF(s.account_source, ''), 'unknown') as account_source,
|
|
1334
|
-
COALESCE(s.request_count, 0) as requests,
|
|
1335
|
-
COALESCE(s.total_tokens, 0) as total_tokens,
|
|
1336
|
-
COALESCE(s.total_cost_usd, 0) as cost_usd,
|
|
1337
|
-
'estimated' as cost_basis,
|
|
1338
|
-
s.started_at as last_active
|
|
1339
|
-
FROM sessions s
|
|
1340
|
-
WHERE ${sessionWhere}${sessionMachineClause}
|
|
1341
|
-
AND s.id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
1342
|
-
AND (s.account_key != '' OR s.account_tool != '' OR s.account_name != '' OR s.account_email != '')
|
|
1343
|
-
`).all(...sessionMachineParams);
|
|
1344
|
-
for (const row of sessionOnlyRows)
|
|
1345
|
-
addAccountBreakdownRow(groups, row, true);
|
|
1346
|
-
const result = [...groups.values()].map((group) => ({
|
|
1347
|
-
account_key: group.account_key,
|
|
1348
|
-
account_tool: group.account_tool,
|
|
1349
|
-
account_name: group.account_name,
|
|
1350
|
-
account_email: group.account_email,
|
|
1351
|
-
account_source: group.account_source,
|
|
1352
|
-
sessions: group.sessionIds.size,
|
|
1353
|
-
requests: group.requests,
|
|
1354
|
-
total_tokens: group.total_tokens,
|
|
1355
|
-
api_equivalent_usd: group.api_equivalent_usd,
|
|
1356
|
-
billable_usd: group.metered_api_usd,
|
|
1357
|
-
metered_api_usd: group.metered_api_usd,
|
|
1358
|
-
subscription_included_usd: group.subscription_included_usd,
|
|
1359
|
-
estimated_usd: group.estimated_usd,
|
|
1360
|
-
unknown_usd: group.unknown_usd,
|
|
1361
|
-
cost_usd: group.api_equivalent_usd,
|
|
1362
|
-
last_active: group.last_active
|
|
1363
|
-
}));
|
|
1364
|
-
result.sort((a, b) => b.cost_usd - a.cost_usd);
|
|
1365
|
-
return result;
|
|
1366
|
-
}
|
|
1367
|
-
function kindFromCostCenterId(id) {
|
|
1368
|
-
const prefix = id.split(":", 1)[0] ?? "";
|
|
1369
|
-
return ["loop", "app", "repo", "service", "team"].includes(prefix) ? prefix : "";
|
|
1370
|
-
}
|
|
1371
|
-
function addCostCenterBreakdownRow(groups, row, sessionOnly) {
|
|
1372
|
-
if (!row.cost_center_id)
|
|
1373
|
-
return;
|
|
1374
|
-
const derivedKind = row.kind || kindFromCostCenterId(row.cost_center_id) || "service";
|
|
1375
|
-
const group = groups.get(row.cost_center_id) ?? {
|
|
1376
|
-
cost_center_id: row.cost_center_id,
|
|
1377
|
-
kind: derivedKind,
|
|
1378
|
-
name: row.name || row.cost_center_id,
|
|
1379
|
-
repo_path: row.repo_path ?? null,
|
|
1380
|
-
labels_json: row.labels_json || "{}",
|
|
1381
|
-
sessionIds: new Set,
|
|
1382
|
-
requests: 0,
|
|
1383
|
-
total_tokens: 0,
|
|
1384
|
-
api_equivalent_usd: 0,
|
|
1385
|
-
metered_api_usd: 0,
|
|
1386
|
-
subscription_included_usd: 0,
|
|
1387
|
-
estimated_usd: 0,
|
|
1388
|
-
unknown_usd: 0,
|
|
1389
|
-
last_active: ""
|
|
1390
|
-
};
|
|
1391
|
-
group.kind = derivedKind;
|
|
1392
|
-
if (row.name)
|
|
1393
|
-
group.name = row.name;
|
|
1394
|
-
if (row.repo_path && !group.repo_path)
|
|
1395
|
-
group.repo_path = row.repo_path;
|
|
1396
|
-
if (row.labels_json && row.labels_json !== "{}")
|
|
1397
|
-
group.labels_json = row.labels_json;
|
|
1398
|
-
if (row.session_id)
|
|
1399
|
-
group.sessionIds.add(row.session_id);
|
|
1400
|
-
group.requests += row.requests;
|
|
1401
|
-
group.total_tokens += row.total_tokens;
|
|
1402
|
-
group.api_equivalent_usd += row.cost_usd;
|
|
1403
|
-
if (sessionOnly) {
|
|
1404
|
-
group.estimated_usd += row.cost_usd;
|
|
1405
|
-
} else if (row.cost_basis === "metered_api") {
|
|
1406
|
-
group.metered_api_usd += row.cost_usd;
|
|
1407
|
-
} else if (row.cost_basis === "subscription_included") {
|
|
1408
|
-
group.subscription_included_usd += row.cost_usd;
|
|
1409
|
-
} else if (row.cost_basis === "unknown") {
|
|
1410
|
-
group.unknown_usd += row.cost_usd;
|
|
1411
|
-
} else {
|
|
1412
|
-
group.estimated_usd += row.cost_usd;
|
|
1413
|
-
}
|
|
1414
|
-
if (!group.last_active || row.last_active > group.last_active)
|
|
1415
|
-
group.last_active = row.last_active;
|
|
1416
|
-
groups.set(row.cost_center_id, group);
|
|
1417
|
-
}
|
|
1418
|
-
function queryCostCenterBreakdown(db, period = "all", filter = {}) {
|
|
1419
|
-
const requestWhere = filter.since ? "r.timestamp >= ?" : requestPeriodWhere(period);
|
|
1420
|
-
const sessionWhere = filter.since ? "s.started_at >= ?" : sessionPeriodWhere(period);
|
|
1421
|
-
const requestMachineClause = filter.machine ? " AND r.machine_id = ?" : "";
|
|
1422
|
-
const sessionMachineClause = filter.machine ? " AND s.machine_id = ?" : "";
|
|
1423
|
-
const requestParams = [];
|
|
1424
|
-
const sessionParams = [];
|
|
1425
|
-
if (filter.since) {
|
|
1426
|
-
requestParams.push(filter.since);
|
|
1427
|
-
sessionParams.push(filter.since);
|
|
1428
|
-
}
|
|
1429
|
-
if (filter.machine) {
|
|
1430
|
-
requestParams.push(filter.machine);
|
|
1431
|
-
sessionParams.push(filter.machine);
|
|
1432
|
-
}
|
|
1433
|
-
const groups = new Map;
|
|
1434
|
-
const requestRows = db.prepare(`
|
|
1435
|
-
SELECT
|
|
1436
|
-
COALESCE(NULLIF(r.cost_center_id, ''), NULLIF(s.cost_center_id, '')) as cost_center_id,
|
|
1437
|
-
COALESCE(cc.kind, '') as kind,
|
|
1438
|
-
COALESCE(cc.name, COALESCE(NULLIF(r.cost_center_id, ''), NULLIF(s.cost_center_id, ''))) as name,
|
|
1439
|
-
cc.repo_path as repo_path,
|
|
1440
|
-
COALESCE(cc.labels_json, '{}') as labels_json,
|
|
1441
|
-
r.session_id as session_id,
|
|
1442
|
-
1 as requests,
|
|
1443
|
-
COALESCE(r.input_tokens + r.output_tokens + r.cache_read_tokens + r.cache_create_tokens, 0) as total_tokens,
|
|
1444
|
-
COALESCE(r.cost_usd, 0) as cost_usd,
|
|
1445
|
-
COALESCE(NULLIF(r.cost_basis, ''), 'estimated') as cost_basis,
|
|
1446
|
-
r.timestamp as last_active
|
|
1447
|
-
FROM requests r
|
|
1448
|
-
LEFT JOIN sessions s ON s.id = r.session_id
|
|
1449
|
-
LEFT JOIN cost_centers cc ON cc.id = COALESCE(NULLIF(r.cost_center_id, ''), NULLIF(s.cost_center_id, ''))
|
|
1450
|
-
WHERE ${requestWhere}${requestMachineClause}
|
|
1451
|
-
AND COALESCE(NULLIF(r.cost_center_id, ''), NULLIF(s.cost_center_id, '')) IS NOT NULL
|
|
1452
|
-
AND COALESCE(NULLIF(r.cost_center_id, ''), NULLIF(s.cost_center_id, '')) != ''
|
|
1453
|
-
`).all(...requestParams);
|
|
1454
|
-
for (const row of requestRows)
|
|
1455
|
-
addCostCenterBreakdownRow(groups, row, false);
|
|
1456
|
-
const sessionOnlyRows = db.prepare(`
|
|
1457
|
-
SELECT
|
|
1458
|
-
s.cost_center_id as cost_center_id,
|
|
1459
|
-
COALESCE(cc.kind, '') as kind,
|
|
1460
|
-
COALESCE(cc.name, s.cost_center_id) as name,
|
|
1461
|
-
cc.repo_path as repo_path,
|
|
1462
|
-
COALESCE(cc.labels_json, '{}') as labels_json,
|
|
1463
|
-
s.id as session_id,
|
|
1464
|
-
COALESCE(s.request_count, 0) as requests,
|
|
1465
|
-
COALESCE(s.total_tokens, 0) as total_tokens,
|
|
1466
|
-
COALESCE(s.total_cost_usd, 0) as cost_usd,
|
|
1467
|
-
'estimated' as cost_basis,
|
|
1468
|
-
s.started_at as last_active
|
|
1469
|
-
FROM sessions s
|
|
1470
|
-
LEFT JOIN cost_centers cc ON cc.id = s.cost_center_id
|
|
1471
|
-
WHERE ${sessionWhere}${sessionMachineClause}
|
|
1472
|
-
AND s.id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
1473
|
-
AND s.cost_center_id IS NOT NULL
|
|
1474
|
-
AND s.cost_center_id != ''
|
|
1475
|
-
`).all(...sessionParams);
|
|
1476
|
-
for (const row of sessionOnlyRows)
|
|
1477
|
-
addCostCenterBreakdownRow(groups, row, true);
|
|
1478
|
-
const result = [...groups.values()].map((group) => ({
|
|
1479
|
-
cost_center_id: group.cost_center_id,
|
|
1480
|
-
kind: group.kind,
|
|
1481
|
-
name: group.name,
|
|
1482
|
-
repo_path: group.repo_path,
|
|
1483
|
-
labels_json: group.labels_json,
|
|
1484
|
-
sessions: group.sessionIds.size,
|
|
1485
|
-
requests: group.requests,
|
|
1486
|
-
total_tokens: group.total_tokens,
|
|
1487
|
-
api_equivalent_usd: group.api_equivalent_usd,
|
|
1488
|
-
billable_usd: group.metered_api_usd,
|
|
1489
|
-
metered_api_usd: group.metered_api_usd,
|
|
1490
|
-
subscription_included_usd: group.subscription_included_usd,
|
|
1491
|
-
estimated_usd: group.estimated_usd,
|
|
1492
|
-
unknown_usd: group.unknown_usd,
|
|
1493
|
-
cost_usd: group.api_equivalent_usd,
|
|
1494
|
-
last_active: group.last_active
|
|
1495
|
-
}));
|
|
1496
|
-
const filtered = filter.kind ? result.filter((row) => row.kind === filter.kind) : result;
|
|
1497
|
-
filtered.sort((a, b) => b.cost_usd - a.cost_usd);
|
|
1498
|
-
return filtered;
|
|
1499
|
-
}
|
|
1500
|
-
function queryProjectBreakdownSince(db, since) {
|
|
1501
|
-
return db.prepare(`
|
|
1502
|
-
SELECT project_path, project_name,
|
|
1503
|
-
COUNT(*) as sessions,
|
|
1504
|
-
COALESCE(SUM(total_tokens), 0) as total_tokens,
|
|
1505
|
-
COALESCE(SUM(request_count), 0) as requests,
|
|
1506
|
-
COALESCE(SUM(total_cost_usd), 0) as cost_usd,
|
|
1507
|
-
MAX(started_at) as last_active
|
|
1508
|
-
FROM sessions WHERE started_at >= ?
|
|
1509
|
-
GROUP BY project_path ORDER BY cost_usd DESC
|
|
1510
|
-
`).all(since);
|
|
1511
|
-
}
|
|
1512
|
-
function queryAgentBreakdownSince(db, since) {
|
|
1513
|
-
return db.prepare(`
|
|
1514
|
-
SELECT agent,
|
|
1515
|
-
COUNT(DISTINCT session_id) as sessions,
|
|
1516
|
-
COUNT(*) as requests,
|
|
1517
|
-
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens,
|
|
1518
|
-
COALESCE(SUM(cost_usd), 0) as api_equivalent_usd,
|
|
1519
|
-
COALESCE(SUM(CASE WHEN cost_basis = 'metered_api' THEN cost_usd ELSE 0 END), 0) as billable_usd,
|
|
1520
|
-
COALESCE(SUM(CASE WHEN cost_basis = 'subscription_included' THEN cost_usd ELSE 0 END), 0) as subscription_included_usd,
|
|
1521
|
-
MAX(timestamp) as last_active
|
|
1522
|
-
FROM requests
|
|
1523
|
-
WHERE timestamp >= ?
|
|
1524
|
-
GROUP BY agent
|
|
1525
|
-
ORDER BY api_equivalent_usd DESC
|
|
1526
|
-
`).all(since);
|
|
1527
|
-
}
|
|
1528
|
-
function queryModelBreakdownSince(db, since) {
|
|
1529
|
-
return db.prepare(`
|
|
1530
|
-
SELECT model, agent,
|
|
1531
|
-
COUNT(*) as requests,
|
|
1532
|
-
COALESCE(SUM(input_tokens), 0) as input_tokens,
|
|
1533
|
-
COALESCE(SUM(output_tokens), 0) as output_tokens,
|
|
1534
|
-
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens,
|
|
1535
|
-
COALESCE(SUM(cost_usd), 0) as cost_usd
|
|
1536
|
-
FROM requests WHERE timestamp >= ?
|
|
1537
|
-
GROUP BY model, agent ORDER BY cost_usd DESC
|
|
1538
|
-
`).all(since);
|
|
1539
|
-
}
|
|
1540
|
-
function queryAccountBreakdownSince(db, since) {
|
|
1541
|
-
return db.prepare(`
|
|
1542
|
-
WITH request_rows AS (
|
|
1543
|
-
SELECT
|
|
1544
|
-
r.session_id as session_id,
|
|
1545
|
-
COALESCE(NULLIF(r.agent, ''), NULLIF(s.agent, ''), NULLIF(r.account_tool, ''), NULLIF(s.account_tool, ''), 'unknown') as account_agent,
|
|
1546
|
-
COALESCE(NULLIF(r.account_key, ''), NULLIF(s.account_key, ''), '') as raw_account_key,
|
|
1547
|
-
COALESCE(NULLIF(r.account_name, ''), NULLIF(s.account_name, ''), '') as raw_account_name,
|
|
1548
|
-
LOWER(TRIM(COALESCE(NULLIF(r.account_email, ''), NULLIF(s.account_email, ''), ''))) as raw_account_email,
|
|
1549
|
-
COALESCE(NULLIF(r.account_source, ''), NULLIF(s.account_source, ''), 'unknown') as account_source,
|
|
1550
|
-
1 as requests,
|
|
1551
|
-
COALESCE(r.input_tokens + r.output_tokens + r.cache_read_tokens + r.cache_create_tokens, 0) as total_tokens,
|
|
1552
|
-
COALESCE(r.cost_usd, 0) as cost_usd,
|
|
1553
|
-
COALESCE(NULLIF(r.cost_basis, ''), 'estimated') as cost_basis,
|
|
1554
|
-
r.timestamp as last_active
|
|
1555
|
-
FROM requests r
|
|
1556
|
-
LEFT JOIN sessions s ON s.id = r.session_id
|
|
1557
|
-
WHERE r.timestamp >= ?
|
|
1558
|
-
AND (
|
|
1559
|
-
COALESCE(NULLIF(r.account_key, ''), NULLIF(s.account_key, ''), '') != ''
|
|
1560
|
-
OR COALESCE(NULLIF(r.account_tool, ''), NULLIF(s.account_tool, ''), '') != ''
|
|
1561
|
-
OR COALESCE(NULLIF(r.account_name, ''), NULLIF(s.account_name, ''), '') != ''
|
|
1562
|
-
OR COALESCE(NULLIF(r.account_email, ''), NULLIF(s.account_email, ''), '') != ''
|
|
1563
|
-
)
|
|
1564
|
-
),
|
|
1565
|
-
session_only_rows AS (
|
|
1566
|
-
SELECT
|
|
1567
|
-
s.id as session_id,
|
|
1568
|
-
COALESCE(NULLIF(s.agent, ''), NULLIF(s.account_tool, ''), 'unknown') as account_agent,
|
|
1569
|
-
s.account_key as raw_account_key,
|
|
1570
|
-
s.account_name as raw_account_name,
|
|
1571
|
-
LOWER(TRIM(COALESCE(s.account_email, ''))) as raw_account_email,
|
|
1572
|
-
COALESCE(NULLIF(s.account_source, ''), 'unknown') as account_source,
|
|
1573
|
-
COALESCE(s.request_count, 0) as requests,
|
|
1574
|
-
COALESCE(s.total_tokens, 0) as total_tokens,
|
|
1575
|
-
COALESCE(s.total_cost_usd, 0) as cost_usd,
|
|
1576
|
-
'estimated' as cost_basis,
|
|
1577
|
-
s.started_at as last_active
|
|
1578
|
-
FROM sessions s
|
|
1579
|
-
WHERE s.started_at >= ?
|
|
1580
|
-
AND s.id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
1581
|
-
AND (s.account_key != '' OR s.account_tool != '' OR s.account_name != '' OR s.account_email != '')
|
|
1582
|
-
),
|
|
1583
|
-
normalized AS (
|
|
1584
|
-
SELECT
|
|
1585
|
-
CASE
|
|
1586
|
-
WHEN raw_account_email != '' THEN account_agent || ':' || raw_account_email
|
|
1587
|
-
WHEN raw_account_name != '' THEN account_agent || ':' || raw_account_name
|
|
1588
|
-
ELSE raw_account_key
|
|
1589
|
-
END as account_key,
|
|
1590
|
-
account_agent as account_tool,
|
|
1591
|
-
raw_account_name as account_name,
|
|
1592
|
-
raw_account_email as account_email,
|
|
1593
|
-
account_source,
|
|
1594
|
-
session_id,
|
|
1595
|
-
requests,
|
|
1596
|
-
total_tokens,
|
|
1597
|
-
cost_usd,
|
|
1598
|
-
cost_basis,
|
|
1599
|
-
last_active
|
|
1600
|
-
FROM request_rows
|
|
1601
|
-
UNION ALL
|
|
1602
|
-
SELECT
|
|
1603
|
-
CASE
|
|
1604
|
-
WHEN raw_account_email != '' THEN account_agent || ':' || raw_account_email
|
|
1605
|
-
WHEN raw_account_name != '' THEN account_agent || ':' || raw_account_name
|
|
1606
|
-
ELSE raw_account_key
|
|
1607
|
-
END as account_key,
|
|
1608
|
-
account_agent as account_tool,
|
|
1609
|
-
raw_account_name as account_name,
|
|
1610
|
-
raw_account_email as account_email,
|
|
1611
|
-
account_source,
|
|
1612
|
-
session_id,
|
|
1613
|
-
requests,
|
|
1614
|
-
total_tokens,
|
|
1615
|
-
cost_usd,
|
|
1616
|
-
cost_basis,
|
|
1617
|
-
last_active
|
|
1618
|
-
FROM session_only_rows
|
|
1619
|
-
)
|
|
1620
|
-
SELECT account_key,
|
|
1621
|
-
account_tool,
|
|
1622
|
-
COALESCE(MAX(NULLIF(account_name, '')), MAX(NULLIF(account_email, '')), account_key) as account_name,
|
|
1623
|
-
NULLIF(account_email, '') as account_email,
|
|
1624
|
-
COALESCE(MAX(NULLIF(account_source, 'unknown')), 'unknown') as account_source,
|
|
1625
|
-
COUNT(DISTINCT session_id) as sessions,
|
|
1626
|
-
COALESCE(SUM(requests), 0) as requests,
|
|
1627
|
-
COALESCE(SUM(total_tokens), 0) as total_tokens,
|
|
1628
|
-
COALESCE(SUM(cost_usd), 0) as api_equivalent_usd,
|
|
1629
|
-
COALESCE(SUM(CASE WHEN cost_basis = 'metered_api' THEN cost_usd ELSE 0 END), 0) as billable_usd,
|
|
1630
|
-
COALESCE(SUM(CASE WHEN cost_basis = 'metered_api' THEN cost_usd ELSE 0 END), 0) as metered_api_usd,
|
|
1631
|
-
COALESCE(SUM(CASE WHEN cost_basis = 'subscription_included' THEN cost_usd ELSE 0 END), 0) as subscription_included_usd,
|
|
1632
|
-
COALESCE(SUM(CASE WHEN cost_basis NOT IN ('metered_api', 'subscription_included', 'unknown') THEN cost_usd ELSE 0 END), 0) as estimated_usd,
|
|
1633
|
-
COALESCE(SUM(CASE WHEN cost_basis = 'unknown' THEN cost_usd ELSE 0 END), 0) as unknown_usd,
|
|
1634
|
-
COALESCE(SUM(cost_usd), 0) as cost_usd,
|
|
1635
|
-
MAX(last_active) as last_active
|
|
1636
|
-
FROM normalized
|
|
1637
|
-
WHERE account_key != ''
|
|
1638
|
-
GROUP BY account_key, account_tool, account_email
|
|
1639
|
-
ORDER BY api_equivalent_usd DESC
|
|
1640
|
-
`).all(since, since);
|
|
1641
|
-
}
|
|
1642
|
-
function queryDailyBreakdown(db, days = 30, machine) {
|
|
1643
|
-
const machineClause = machine ? " AND machine_id = ?" : "";
|
|
1644
|
-
const params = machine ? [`-${days}`, machine] : [`-${days}`];
|
|
1645
|
-
return db.prepare(`
|
|
1646
|
-
SELECT DATE(timestamp) as date, agent, COALESCE(SUM(cost_usd), 0) as cost_usd
|
|
1647
|
-
FROM requests
|
|
1648
|
-
WHERE timestamp >= DATE('now', ? || ' days')${machineClause}
|
|
1649
|
-
GROUP BY DATE(timestamp), agent
|
|
1650
|
-
ORDER BY date ASC
|
|
1651
|
-
`).all(...params);
|
|
1652
|
-
}
|
|
1653
|
-
function upsertProject(db, project) {
|
|
1654
|
-
db.prepare(`
|
|
1655
|
-
INSERT OR REPLACE INTO projects (id, path, name, description, tags, created_at)
|
|
1656
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
1657
|
-
`).run(project.id, project.path, project.name, project.description ?? null, JSON.stringify(project.tags), project.created_at);
|
|
1658
|
-
}
|
|
1659
|
-
function getProject(db, path) {
|
|
1660
|
-
const row = db.prepare(`SELECT * FROM projects WHERE path = ?`).get(path);
|
|
1661
|
-
if (!row)
|
|
1662
|
-
return null;
|
|
1663
|
-
return { ...row, tags: JSON.parse(row["tags"] ?? "[]") };
|
|
1664
|
-
}
|
|
1665
|
-
function deleteProject(db, path) {
|
|
1666
|
-
db.prepare(`DELETE FROM projects WHERE path = ?`).run(path);
|
|
1667
|
-
}
|
|
1668
|
-
function upsertBudget(db, budget) {
|
|
1669
|
-
db.prepare(`
|
|
1670
|
-
INSERT OR REPLACE INTO budgets
|
|
1671
|
-
(id, project_path, agent, cost_center_id, period, limit_usd, alert_at_percent, created_at, updated_at)
|
|
1672
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1673
|
-
`).run(budget.id, budget.project_path ?? null, budget.agent ?? null, budget.cost_center_id ?? null, budget.period, budget.limit_usd, budget.alert_at_percent, budget.created_at, budget.updated_at);
|
|
1674
|
-
}
|
|
1675
|
-
function listBudgets(db) {
|
|
1676
|
-
return db.prepare(`SELECT * FROM budgets ORDER BY created_at DESC`).all();
|
|
1677
|
-
}
|
|
1678
|
-
function deleteBudget(db, id) {
|
|
1679
|
-
db.prepare(`DELETE FROM budgets WHERE id = ?`).run(id);
|
|
1680
|
-
}
|
|
1681
|
-
function getBudgetStatuses(db) {
|
|
1682
|
-
const budgets = listBudgets(db);
|
|
1683
|
-
return budgets.map((b) => {
|
|
1684
|
-
const periodStart = b.period === "daily" ? "DATE('now')" : b.period === "weekly" ? "DATE('now', '-7 days')" : "DATE('now', '-30 days')";
|
|
1685
|
-
let spendQuery = `SELECT COALESCE(SUM(cost_usd), 0) as spend FROM requests WHERE timestamp >= ${periodStart}`;
|
|
1686
|
-
const params = [];
|
|
1687
|
-
if (b.project_path) {
|
|
1688
|
-
spendQuery += ` AND session_id IN (SELECT id FROM sessions WHERE project_path = ?)`;
|
|
1689
|
-
params.push(b.project_path);
|
|
1690
|
-
}
|
|
1691
|
-
if (b.agent) {
|
|
1692
|
-
spendQuery += ` AND agent = ?`;
|
|
1693
|
-
params.push(b.agent);
|
|
1694
|
-
}
|
|
1695
|
-
if (b.cost_center_id) {
|
|
1696
|
-
spendQuery += ` AND COALESCE(NULLIF(cost_center_id, ''), (SELECT NULLIF(s.cost_center_id, '') FROM sessions s WHERE s.id = requests.session_id)) = ?`;
|
|
1697
|
-
params.push(b.cost_center_id);
|
|
1698
|
-
}
|
|
1699
|
-
const row = db.prepare(spendQuery).get(...params);
|
|
1700
|
-
const spend = row.spend;
|
|
1701
|
-
const percent = b.limit_usd > 0 ? spend / b.limit_usd * 100 : 0;
|
|
1702
|
-
return {
|
|
1703
|
-
...b,
|
|
1704
|
-
current_spend_usd: spend,
|
|
1705
|
-
percent_used: percent,
|
|
1706
|
-
is_over_limit: percent >= 100,
|
|
1707
|
-
is_over_alert: percent >= b.alert_at_percent
|
|
1708
|
-
};
|
|
1709
|
-
});
|
|
1710
|
-
}
|
|
1711
|
-
function upsertGoal(db, goal) {
|
|
1712
|
-
db.prepare(`
|
|
1713
|
-
INSERT OR REPLACE INTO goals
|
|
1714
|
-
(id, period, project_path, agent, limit_usd, created_at, updated_at)
|
|
1715
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
1716
|
-
`).run(goal.id, goal.period, goal.project_path ?? null, goal.agent ?? null, goal.limit_usd, goal.created_at, goal.updated_at);
|
|
1717
|
-
}
|
|
1718
|
-
function deleteGoal(db, id) {
|
|
1719
|
-
db.prepare(`DELETE FROM goals WHERE id = ?`).run(id);
|
|
1720
|
-
}
|
|
1721
|
-
function listGoals(db) {
|
|
1722
|
-
return db.prepare(`SELECT * FROM goals ORDER BY created_at DESC`).all();
|
|
1723
|
-
}
|
|
1724
|
-
function getGoalStatuses(db) {
|
|
1725
|
-
const goals = listGoals(db);
|
|
1726
|
-
return goals.map((g) => {
|
|
1727
|
-
const periodStart = g.period === "day" ? "DATE('now')" : g.period === "week" ? "DATE('now', '-7 days')" : g.period === "month" ? "DATE('now', '-30 days')" : "DATE('now', '-365 days')";
|
|
1728
|
-
let spendQuery = `SELECT COALESCE(SUM(cost_usd), 0) as spend FROM requests WHERE timestamp >= ${periodStart}`;
|
|
1729
|
-
const params = [];
|
|
1730
|
-
if (g.project_path) {
|
|
1731
|
-
spendQuery += ` AND session_id IN (SELECT id FROM sessions WHERE project_path = ?)`;
|
|
1732
|
-
params.push(g.project_path);
|
|
1733
|
-
}
|
|
1734
|
-
if (g.agent) {
|
|
1735
|
-
spendQuery += ` AND agent = ?`;
|
|
1736
|
-
params.push(g.agent);
|
|
1737
|
-
}
|
|
1738
|
-
const row = db.prepare(spendQuery).get(...params);
|
|
1739
|
-
const spend = row.spend;
|
|
1740
|
-
const percent = g.limit_usd > 0 ? spend / g.limit_usd * 100 : 0;
|
|
1741
|
-
return {
|
|
1742
|
-
...g,
|
|
1743
|
-
current_spend_usd: spend,
|
|
1744
|
-
percent_used: percent,
|
|
1745
|
-
is_on_track: percent < 70,
|
|
1746
|
-
is_at_risk: percent >= 70 && percent <= 100,
|
|
1747
|
-
is_over: percent > 100
|
|
1748
|
-
};
|
|
1749
|
-
});
|
|
1750
|
-
}
|
|
1751
|
-
function queryRequestsSince(db, since) {
|
|
1752
|
-
return db.prepare(`SELECT * FROM requests WHERE timestamp > ? ORDER BY timestamp ASC`).all(since);
|
|
1753
|
-
}
|
|
1754
|
-
function insertFeedback(db, record) {
|
|
1755
|
-
db.prepare(`INSERT INTO feedback (message, email, category, version, machine_id) VALUES (?, ?, ?, ?, ?)`).run(record.message, record.email ?? null, record.category ?? "general", record.version ?? null, record.machine_id ?? null);
|
|
1756
|
-
}
|
|
1757
|
-
function queryBillingSummary(db, period) {
|
|
1758
|
-
const where = period === "today" ? `date = DATE('now')` : period === "yesterday" ? `date = DATE('now', '-1 day')` : period === "week" ? `date >= DATE('now', 'weekday 0', '-7 days')` : period === "month" ? `date >= DATE('now', 'start of month')` : period === "year" ? `date >= DATE('now', 'start of year')` : "1=1";
|
|
1759
|
-
const rows = db.prepare(`SELECT provider, SUM(cost_usd) as cost FROM billing_daily WHERE ${where} GROUP BY provider`).all();
|
|
1760
|
-
const by_provider = {};
|
|
1761
|
-
let total = 0;
|
|
1762
|
-
for (const r of rows) {
|
|
1763
|
-
by_provider[r.provider] = r.cost;
|
|
1764
|
-
total += r.cost;
|
|
1765
|
-
}
|
|
1766
|
-
return { total_usd: total, by_provider };
|
|
1767
|
-
}
|
|
1768
|
-
function getSessionDetail(db, id) {
|
|
1769
|
-
const session = db.prepare(`SELECT * FROM sessions WHERE id = ? OR id LIKE ?`).get(id, `${id}%`);
|
|
1770
|
-
if (!session)
|
|
1771
|
-
return null;
|
|
1772
|
-
const requests = db.prepare(`SELECT * FROM requests WHERE session_id = ? ORDER BY timestamp ASC`).all(session["id"]);
|
|
1773
|
-
return { session, requests };
|
|
1774
|
-
}
|
|
1775
|
-
function listMachines(db, period = "all") {
|
|
1776
|
-
const rWhere = requestPeriodWhere(period);
|
|
1777
|
-
const sWhere = sessionPeriodWhere(period);
|
|
1778
|
-
return db.prepare(`
|
|
1779
|
-
WITH request_stats AS (
|
|
1780
|
-
SELECT
|
|
1781
|
-
machine_id,
|
|
1782
|
-
COUNT(DISTINCT session_id) as sessions,
|
|
1783
|
-
COUNT(*) as requests,
|
|
1784
|
-
COALESCE(SUM(cost_usd), 0) as total_cost_usd,
|
|
1785
|
-
MAX(timestamp) as last_active
|
|
1786
|
-
FROM requests
|
|
1787
|
-
WHERE machine_id != ''
|
|
1788
|
-
AND ${rWhere}
|
|
1789
|
-
GROUP BY machine_id
|
|
1790
|
-
),
|
|
1791
|
-
session_only_stats AS (
|
|
1792
|
-
SELECT
|
|
1793
|
-
machine_id,
|
|
1794
|
-
COUNT(*) as sessions,
|
|
1795
|
-
COALESCE(SUM(request_count), 0) as requests,
|
|
1796
|
-
COALESCE(SUM(total_cost_usd), 0) as total_cost_usd,
|
|
1797
|
-
MAX(started_at) as last_active
|
|
1798
|
-
FROM sessions
|
|
1799
|
-
WHERE machine_id != ''
|
|
1800
|
-
AND ${sWhere}
|
|
1801
|
-
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
1802
|
-
GROUP BY machine_id
|
|
1803
|
-
),
|
|
1804
|
-
combined AS (
|
|
1805
|
-
SELECT * FROM request_stats
|
|
1806
|
-
UNION ALL
|
|
1807
|
-
SELECT * FROM session_only_stats
|
|
1808
|
-
)
|
|
1809
|
-
SELECT
|
|
1810
|
-
machine_id,
|
|
1811
|
-
COALESCE(SUM(sessions), 0) as sessions,
|
|
1812
|
-
COALESCE(SUM(requests), 0) as requests,
|
|
1813
|
-
COALESCE(SUM(total_cost_usd), 0) as total_cost_usd,
|
|
1814
|
-
MAX(last_active) as last_active
|
|
1815
|
-
FROM combined
|
|
1816
|
-
GROUP BY machine_id
|
|
1817
|
-
ORDER BY total_cost_usd DESC
|
|
1818
|
-
`).all();
|
|
1819
|
-
}
|
|
1820
|
-
function upsertModelPricing(db, p) {
|
|
1821
|
-
db.prepare(`
|
|
1822
|
-
INSERT OR REPLACE INTO model_pricing
|
|
1823
|
-
(model, input_per_1m, output_per_1m, cache_read_per_1m, cache_write_per_1m, cache_write_1h_per_1m, cache_storage_per_1m_hour, updated_at)
|
|
1824
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1825
|
-
`).run(p.model, p.input_per_1m, p.output_per_1m, p.cache_read_per_1m, p.cache_write_per_1m, p.cache_write_1h_per_1m ?? 0, p.cache_storage_per_1m_hour ?? 0, p.updated_at);
|
|
1826
|
-
}
|
|
1827
|
-
function getModelPricing(db, model) {
|
|
1828
|
-
return db.prepare(`SELECT * FROM model_pricing WHERE model = ?`).get(model);
|
|
1829
|
-
}
|
|
1830
|
-
function listModelPricing(db) {
|
|
1831
|
-
return db.prepare(`SELECT * FROM model_pricing ORDER BY model ASC`).all();
|
|
1832
|
-
}
|
|
1833
|
-
function deleteModelPricing(db, model) {
|
|
1834
|
-
db.prepare(`DELETE FROM model_pricing WHERE model = ?`).run(model);
|
|
1835
|
-
}
|
|
1836
|
-
function seedModelPricing(db, defaults) {
|
|
1837
|
-
const existing = new Set(db.prepare(`SELECT model FROM model_pricing`).all().map((r) => r.model));
|
|
1838
|
-
const now = new Date().toISOString();
|
|
1839
|
-
for (const [model, p] of Object.entries(defaults)) {
|
|
1840
|
-
if (existing.has(model))
|
|
1841
|
-
continue;
|
|
1842
|
-
upsertModelPricing(db, {
|
|
1843
|
-
model,
|
|
1844
|
-
input_per_1m: p.inputPer1M,
|
|
1845
|
-
output_per_1m: p.outputPer1M,
|
|
1846
|
-
cache_read_per_1m: p.cacheReadPer1M,
|
|
1847
|
-
cache_write_per_1m: p.cacheWritePer1M,
|
|
1848
|
-
cache_write_1h_per_1m: p.cacheWrite1hPer1M ?? 0,
|
|
1849
|
-
cache_storage_per_1m_hour: p.cacheStoragePer1MHour ?? 0,
|
|
1850
|
-
updated_at: now
|
|
1851
|
-
});
|
|
1852
|
-
}
|
|
1853
|
-
}
|
|
1854
|
-
function upsertSubscription(db, sub) {
|
|
1855
|
-
db.prepare(`
|
|
1856
|
-
INSERT OR REPLACE INTO subscriptions
|
|
1857
|
-
(id, agent, provider, plan, monthly_fee_usd, included_usage_usd, billing_cycle_start, reset_policy, active, created_at, updated_at)
|
|
1858
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1859
|
-
`).run(sub.id, sub.agent, sub.provider, sub.plan, sub.monthly_fee_usd, sub.included_usage_usd, sub.billing_cycle_start, sub.reset_policy, sub.active, sub.created_at, sub.updated_at);
|
|
1860
|
-
}
|
|
1861
|
-
function listSubscriptions(db) {
|
|
1862
|
-
return db.prepare(`SELECT * FROM subscriptions ORDER BY provider, plan`).all();
|
|
1863
|
-
}
|
|
1864
|
-
function deleteSubscription(db, id) {
|
|
1865
|
-
db.prepare(`DELETE FROM subscriptions WHERE id = ?`).run(id);
|
|
1866
|
-
}
|
|
1867
|
-
function queryUsageSnapshots(db, opts = {}) {
|
|
1868
|
-
const conditions = [];
|
|
1869
|
-
const params = [];
|
|
1870
|
-
if (opts.agent) {
|
|
1871
|
-
conditions.push("agent = ?");
|
|
1872
|
-
params.push(opts.agent);
|
|
1873
|
-
}
|
|
1874
|
-
if (opts.date) {
|
|
1875
|
-
conditions.push("date = ?");
|
|
1876
|
-
params.push(opts.date);
|
|
1877
|
-
}
|
|
1878
|
-
if (opts.since) {
|
|
1879
|
-
conditions.push("date >= ?");
|
|
1880
|
-
params.push(opts.since);
|
|
1881
|
-
}
|
|
1882
|
-
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1883
|
-
return db.prepare(`SELECT * FROM usage_snapshots ${where} ORDER BY date DESC, agent, metric`).all(...params);
|
|
1884
|
-
}
|
|
1885
|
-
function listMachineRegistry(db) {
|
|
1886
|
-
return db.prepare(`SELECT * FROM machines ORDER BY last_seen_at DESC`).all();
|
|
1887
|
-
}
|
|
1888
|
-
var init_database = () => {};
|
|
1889
|
-
|
|
1890
|
-
// src/lib/agents.ts
|
|
1891
|
-
var AGENTS = [
|
|
1892
|
-
"claude",
|
|
1893
|
-
"takumi",
|
|
1894
|
-
"codex",
|
|
1895
|
-
"gemini",
|
|
1896
|
-
"opencode",
|
|
1897
|
-
"cursor",
|
|
1898
|
-
"pi",
|
|
1899
|
-
"hermes"
|
|
1900
|
-
];
|
|
1901
|
-
var COST_BASIS = [
|
|
1902
|
-
"metered_api",
|
|
1903
|
-
"subscription_included",
|
|
1904
|
-
"estimated",
|
|
1905
|
-
"unknown"
|
|
1906
|
-
];
|
|
1907
|
-
function isAgent(value) {
|
|
1908
|
-
return AGENTS.includes(value);
|
|
1909
|
-
}
|
|
1910
|
-
// src/lib/store/index.ts
|
|
1911
|
-
init_database();
|
|
1912
|
-
init_pricing();
|
|
1913
|
-
import { randomUUID } from "crypto";
|
|
1914
|
-
|
|
1915
|
-
// src/lib/package-metadata.ts
|
|
1916
|
-
import { readFileSync } from "fs";
|
|
1917
|
-
var cachedMetadata = null;
|
|
1918
|
-
function getPackageMetadata() {
|
|
1919
|
-
if (cachedMetadata)
|
|
1920
|
-
return cachedMetadata;
|
|
1921
|
-
const raw = readFileSync(new URL("../../package.json", import.meta.url), "utf8");
|
|
1922
|
-
const parsed = JSON.parse(raw);
|
|
1923
|
-
cachedMetadata = {
|
|
1924
|
-
name: parsed.name ?? "@hasna/economy",
|
|
1925
|
-
version: parsed.version ?? "0.0.0"
|
|
1926
|
-
};
|
|
1927
|
-
return cachedMetadata;
|
|
1928
|
-
}
|
|
1929
|
-
var packageMetadata = getPackageMetadata();
|
|
1930
|
-
|
|
1931
|
-
// src/lib/savings.ts
|
|
1932
|
-
function periodWhere2(period, column) {
|
|
1933
|
-
switch (period) {
|
|
1934
|
-
case "today":
|
|
1935
|
-
return `DATE(${column}) = DATE('now')`;
|
|
1936
|
-
case "yesterday":
|
|
1937
|
-
return `DATE(${column}) = DATE('now', '-1 day')`;
|
|
1938
|
-
case "week":
|
|
1939
|
-
return `${column} >= DATE('now', 'weekday 0', '-7 days')`;
|
|
1940
|
-
case "month":
|
|
1941
|
-
return `${column} >= DATE('now', 'start of month')`;
|
|
1942
|
-
case "year":
|
|
1943
|
-
return `${column} >= DATE('now', 'start of year')`;
|
|
1944
|
-
case "all":
|
|
1945
|
-
return "1=1";
|
|
1946
|
-
}
|
|
1947
|
-
}
|
|
1948
|
-
function prorateMonthlyFee(monthlyFee, period) {
|
|
1949
|
-
const now = new Date;
|
|
1950
|
-
const daysInMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();
|
|
1951
|
-
switch (period) {
|
|
1952
|
-
case "today":
|
|
1953
|
-
case "yesterday":
|
|
1954
|
-
return monthlyFee / daysInMonth;
|
|
1955
|
-
case "week":
|
|
1956
|
-
return monthlyFee / daysInMonth * 7;
|
|
1957
|
-
case "month":
|
|
1958
|
-
return monthlyFee;
|
|
1959
|
-
case "year":
|
|
1960
|
-
return monthlyFee * 12;
|
|
1961
|
-
case "all":
|
|
1962
|
-
return monthlyFee;
|
|
1963
|
-
}
|
|
1964
|
-
}
|
|
1965
|
-
function proratedIncludedConsumed(includedUsage, includedCap, period) {
|
|
1966
|
-
const cap = prorateMonthlyFee(includedCap, period);
|
|
1967
|
-
return cap > 0 ? Math.min(includedUsage, cap) : includedUsage;
|
|
1968
|
-
}
|
|
1969
|
-
function computeSavedUsd(apiEquivalent, onDemand, subscriptionFee) {
|
|
1970
|
-
return Math.max(0, apiEquivalent - onDemand - subscriptionFee);
|
|
1971
|
-
}
|
|
1972
|
-
function querySavingsSummary(db, period, agent) {
|
|
1973
|
-
const where = periodWhere2(period, "timestamp");
|
|
1974
|
-
const agentClause = agent ? " AND agent = ?" : "";
|
|
1975
|
-
const params = agent ? [agent] : [];
|
|
1976
|
-
const apiRow = db.prepare(`
|
|
1977
|
-
SELECT COALESCE(SUM(cost_usd), 0) as total
|
|
1978
|
-
FROM requests
|
|
1979
|
-
WHERE ${where}${agentClause}
|
|
1980
|
-
AND COALESCE(cost_basis, 'estimated') IN ('metered_api', 'estimated', 'unknown')
|
|
1981
|
-
`).get(...params);
|
|
1982
|
-
const includedRow = db.prepare(`
|
|
1983
|
-
SELECT COALESCE(SUM(cost_usd), 0) as total
|
|
1984
|
-
FROM requests
|
|
1985
|
-
WHERE ${where}${agentClause}
|
|
1986
|
-
AND cost_basis = 'subscription_included'
|
|
1987
|
-
`).get(...params);
|
|
1988
|
-
const subWhere = periodWhere2(period, "date");
|
|
1989
|
-
const onDemandRow = db.prepare(`
|
|
1990
|
-
SELECT COALESCE(SUM(value), 0) as total
|
|
1991
|
-
FROM usage_snapshots
|
|
1992
|
-
WHERE ${subWhere}${agent ? " AND agent = ?" : ""}
|
|
1993
|
-
AND metric = 'on_demand_usd'
|
|
1994
|
-
`).get(...params);
|
|
1995
|
-
const subs = db.prepare(`
|
|
1996
|
-
SELECT
|
|
1997
|
-
COALESCE(SUM(monthly_fee_usd), 0) as fee,
|
|
1998
|
-
COALESCE(SUM(included_usage_usd), 0) as included
|
|
1999
|
-
FROM subscriptions
|
|
2000
|
-
WHERE active = 1${agent ? " AND (agent = ? OR agent IS NULL)" : ""}
|
|
2001
|
-
`).get(...agent ? [agent] : []);
|
|
2002
|
-
const subscriptionFee = prorateMonthlyFee(subs.fee, period);
|
|
2003
|
-
const apiEquivalent = apiRow.total + includedRow.total;
|
|
2004
|
-
const includedConsumed = proratedIncludedConsumed(includedRow.total, subs.included, period);
|
|
2005
|
-
const onDemand = onDemandRow.total;
|
|
2006
|
-
const saved = computeSavedUsd(apiEquivalent, onDemand, subscriptionFee);
|
|
2007
|
-
const byAgent = {};
|
|
2008
|
-
if (!agent) {
|
|
2009
|
-
const onDemandByAgent = new Map;
|
|
2010
|
-
for (const row of db.prepare(`
|
|
2011
|
-
SELECT agent, COALESCE(SUM(value), 0) as total
|
|
2012
|
-
FROM usage_snapshots
|
|
2013
|
-
WHERE ${subWhere}
|
|
2014
|
-
AND metric = 'on_demand_usd'
|
|
2015
|
-
GROUP BY agent
|
|
2016
|
-
`).all()) {
|
|
2017
|
-
onDemandByAgent.set(row.agent, row.total);
|
|
2018
|
-
}
|
|
2019
|
-
const subscriptionByAgent = new Map;
|
|
2020
|
-
for (const row of db.prepare(`
|
|
2021
|
-
SELECT agent,
|
|
2022
|
-
COALESCE(SUM(monthly_fee_usd), 0) as fee,
|
|
2023
|
-
COALESCE(SUM(included_usage_usd), 0) as included
|
|
2024
|
-
FROM subscriptions
|
|
2025
|
-
WHERE active = 1 AND agent IS NOT NULL
|
|
2026
|
-
GROUP BY agent
|
|
2027
|
-
`).all()) {
|
|
2028
|
-
subscriptionByAgent.set(row.agent, row);
|
|
2029
|
-
}
|
|
2030
|
-
const globalSubs = db.prepare(`
|
|
2031
|
-
SELECT
|
|
2032
|
-
COALESCE(SUM(monthly_fee_usd), 0) as fee,
|
|
2033
|
-
COALESCE(SUM(included_usage_usd), 0) as included
|
|
2034
|
-
FROM subscriptions
|
|
2035
|
-
WHERE active = 1 AND agent IS NULL
|
|
2036
|
-
`).get();
|
|
2037
|
-
const rows = db.prepare(`
|
|
2038
|
-
SELECT agent,
|
|
2039
|
-
COALESCE(SUM(cost_usd), 0) as api_eq,
|
|
2040
|
-
COALESCE(SUM(CASE WHEN cost_basis = 'subscription_included' THEN cost_usd ELSE 0 END), 0) as included
|
|
2041
|
-
FROM requests WHERE ${where}
|
|
2042
|
-
GROUP BY agent
|
|
2043
|
-
`).all();
|
|
2044
|
-
const totalAgentApiEq = rows.reduce((sum, row) => sum + row.api_eq, 0);
|
|
2045
|
-
for (const row of db.prepare(`
|
|
2046
|
-
SELECT agent,
|
|
2047
|
-
COALESCE(SUM(cost_usd), 0) as api_eq,
|
|
2048
|
-
COALESCE(SUM(CASE WHEN cost_basis = 'subscription_included' THEN cost_usd ELSE 0 END), 0) as included
|
|
2049
|
-
FROM requests WHERE ${where}
|
|
2050
|
-
GROUP BY agent
|
|
2051
|
-
`).all()) {
|
|
2052
|
-
const agentSubs = subscriptionByAgent.get(row.agent) ?? { fee: 0, included: 0 };
|
|
2053
|
-
const globalShare = totalAgentApiEq > 0 ? row.api_eq / totalAgentApiEq : 0;
|
|
2054
|
-
const agentFee = prorateMonthlyFee(agentSubs.fee + globalSubs.fee * globalShare, period);
|
|
2055
|
-
const agentIncludedCap = agentSubs.included + globalSubs.included * globalShare;
|
|
2056
|
-
const agentIncludedConsumed = proratedIncludedConsumed(row.included, agentIncludedCap, period);
|
|
2057
|
-
const agentOnDemand = onDemandByAgent.get(row.agent) ?? 0;
|
|
2058
|
-
byAgent[row.agent] = {
|
|
2059
|
-
api_equivalent_usd: row.api_eq,
|
|
2060
|
-
subscription_fee_usd: agentFee,
|
|
2061
|
-
included_consumed_usd: agentIncludedConsumed,
|
|
2062
|
-
on_demand_usd: agentOnDemand,
|
|
2063
|
-
saved_usd: computeSavedUsd(row.api_eq, agentOnDemand, agentFee)
|
|
2064
|
-
};
|
|
2065
|
-
}
|
|
2066
|
-
}
|
|
2067
|
-
return {
|
|
2068
|
-
period,
|
|
2069
|
-
api_equivalent_usd: apiEquivalent,
|
|
2070
|
-
subscription_fee_usd: subscriptionFee,
|
|
2071
|
-
included_consumed_usd: includedConsumed,
|
|
2072
|
-
on_demand_usd: onDemand,
|
|
2073
|
-
saved_usd: saved,
|
|
2074
|
-
by_agent: byAgent
|
|
2075
|
-
};
|
|
2076
|
-
}
|
|
2077
|
-
|
|
2078
|
-
// src/lib/billing-diff.ts
|
|
2079
|
-
init_database();
|
|
2080
|
-
var PROVIDER_TO_AGENT = {
|
|
2081
|
-
anthropic: "claude",
|
|
2082
|
-
openai: "codex",
|
|
2083
|
-
gemini: "gemini",
|
|
2084
|
-
google: "gemini"
|
|
2085
|
-
};
|
|
2086
|
-
function queryBillingDiff(db, period, thresholdPct = 15) {
|
|
2087
|
-
const estimated = querySummary(db, period, undefined, true);
|
|
2088
|
-
const actual = queryBillingSummary(db, period);
|
|
2089
|
-
const delta = estimated.total_usd - actual.total_usd;
|
|
2090
|
-
const deltaPct = actual.total_usd > 0 ? delta / actual.total_usd * 100 : 0;
|
|
2091
|
-
const agentRows = db.prepare(`
|
|
2092
|
-
SELECT agent, COALESCE(SUM(cost_usd), 0) as estimated_usd
|
|
2093
|
-
FROM requests
|
|
2094
|
-
WHERE ${periodWhere3(period, "timestamp")}
|
|
2095
|
-
GROUP BY agent
|
|
2096
|
-
`).all();
|
|
2097
|
-
const by_agent = agentRows.map((row) => {
|
|
2098
|
-
const provider = Object.entries(PROVIDER_TO_AGENT).find(([, a]) => a === row.agent)?.[0];
|
|
2099
|
-
const actualUsd = provider ? actual.by_provider[provider] ?? 0 : 0;
|
|
2100
|
-
const rowDelta = row.estimated_usd - actualUsd;
|
|
2101
|
-
const rowPct = actualUsd > 0 ? rowDelta / actualUsd * 100 : 0;
|
|
2102
|
-
return {
|
|
2103
|
-
agent: row.agent,
|
|
2104
|
-
estimated_usd: row.estimated_usd,
|
|
2105
|
-
actual_usd: actualUsd,
|
|
2106
|
-
delta_usd: rowDelta,
|
|
2107
|
-
delta_pct: rowPct
|
|
2108
|
-
};
|
|
2109
|
-
}).sort((a, b) => Math.abs(b.delta_usd) - Math.abs(a.delta_usd));
|
|
2110
|
-
return {
|
|
2111
|
-
period,
|
|
2112
|
-
estimated_usd: estimated.total_usd,
|
|
2113
|
-
actual_usd: actual.total_usd,
|
|
2114
|
-
delta_usd: delta,
|
|
2115
|
-
delta_pct: deltaPct,
|
|
2116
|
-
threshold_pct: thresholdPct,
|
|
2117
|
-
is_alert: Math.abs(deltaPct) > thresholdPct,
|
|
2118
|
-
by_agent,
|
|
2119
|
-
by_provider: actual.by_provider
|
|
2120
|
-
};
|
|
2121
|
-
}
|
|
2122
|
-
function periodWhere3(period, column) {
|
|
2123
|
-
switch (period) {
|
|
2124
|
-
case "today":
|
|
2125
|
-
return `DATE(${column}) = DATE('now')`;
|
|
2126
|
-
case "yesterday":
|
|
2127
|
-
return `DATE(${column}) = DATE('now', '-1 day')`;
|
|
2128
|
-
case "week":
|
|
2129
|
-
return `${column} >= DATE('now', 'weekday 0', '-7 days')`;
|
|
2130
|
-
case "month":
|
|
2131
|
-
return `${column} >= DATE('now', 'start of month')`;
|
|
2132
|
-
case "year":
|
|
2133
|
-
return `${column} >= DATE('now', 'start of year')`;
|
|
2134
|
-
case "all":
|
|
2135
|
-
return "1=1";
|
|
2136
|
-
}
|
|
2137
|
-
}
|
|
2138
|
-
|
|
2139
|
-
// src/lib/periods.ts
|
|
2140
|
-
function ymd(date) {
|
|
2141
|
-
return date.toISOString().substring(0, 10);
|
|
2142
|
-
}
|
|
2143
|
-
function usageSnapshotFilterForPeriod(period) {
|
|
2144
|
-
const now = new Date;
|
|
2145
|
-
switch (period) {
|
|
2146
|
-
case "today":
|
|
2147
|
-
return { date: ymd(now) };
|
|
2148
|
-
case "yesterday": {
|
|
2149
|
-
const yesterday = new Date(now);
|
|
2150
|
-
yesterday.setUTCDate(yesterday.getUTCDate() - 1);
|
|
2151
|
-
return { date: ymd(yesterday) };
|
|
2152
|
-
}
|
|
2153
|
-
case "week": {
|
|
2154
|
-
const weekAgo = new Date(now);
|
|
2155
|
-
weekAgo.setUTCDate(weekAgo.getUTCDate() - 7);
|
|
2156
|
-
return { since: ymd(weekAgo) };
|
|
2157
|
-
}
|
|
2158
|
-
case "month":
|
|
2159
|
-
return { since: ymd(new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1))) };
|
|
2160
|
-
case "year":
|
|
2161
|
-
return { since: ymd(new Date(Date.UTC(now.getUTCFullYear(), 0, 1))) };
|
|
2162
|
-
case "all":
|
|
2163
|
-
return {};
|
|
2164
|
-
}
|
|
2165
|
-
}
|
|
2166
|
-
|
|
2167
|
-
// src/lib/brief.ts
|
|
2168
|
-
init_database();
|
|
2169
|
-
var ZERO_REQUEST_TOTALS = {
|
|
2170
|
-
sessions: 0,
|
|
2171
|
-
request_sessions: 0,
|
|
2172
|
-
requests: 0,
|
|
2173
|
-
input_tokens: 0,
|
|
2174
|
-
output_tokens: 0,
|
|
2175
|
-
cache_read_tokens: 0,
|
|
2176
|
-
cache_create_tokens: 0,
|
|
2177
|
-
cache_create_5m_tokens: 0,
|
|
2178
|
-
cache_create_1h_tokens: 0,
|
|
2179
|
-
total_tokens: 0,
|
|
2180
|
-
cost_usd: 0,
|
|
2181
|
-
last_active: null
|
|
2182
|
-
};
|
|
2183
|
-
var ZERO_SESSION_TOTALS = {
|
|
2184
|
-
sessions: 0,
|
|
2185
|
-
requests: 0,
|
|
2186
|
-
total_tokens: 0,
|
|
2187
|
-
cost_usd: 0,
|
|
2188
|
-
last_active: null
|
|
2189
|
-
};
|
|
2190
|
-
function formatAge(timestamp, now) {
|
|
2191
|
-
if (!timestamp)
|
|
2192
|
-
return "never";
|
|
2193
|
-
const t = new Date(timestamp).getTime();
|
|
2194
|
-
if (!Number.isFinite(t))
|
|
2195
|
-
return "unknown";
|
|
2196
|
-
const ageMs = Math.max(0, now.getTime() - t);
|
|
2197
|
-
const minutes = Math.floor(ageMs / 60000);
|
|
2198
|
-
if (minutes < 1)
|
|
2199
|
-
return "<1m";
|
|
2200
|
-
if (minutes < 60)
|
|
2201
|
-
return `${minutes}m`;
|
|
2202
|
-
const hours = Math.floor(minutes / 60);
|
|
2203
|
-
if (hours < 48)
|
|
2204
|
-
return `${hours}h`;
|
|
2205
|
-
return `${Math.floor(hours / 24)}d`;
|
|
2206
|
-
}
|
|
2207
|
-
function machineFilter(machine) {
|
|
2208
|
-
if (!machine || machine === "all")
|
|
2209
|
-
return;
|
|
2210
|
-
return machine;
|
|
2211
|
-
}
|
|
2212
|
-
function requestPeriodWhere2(period) {
|
|
2213
|
-
if (period === "today")
|
|
2214
|
-
return `DATE(timestamp) = DATE('now')`;
|
|
2215
|
-
return `timestamp >= DATE('now', 'weekday 0', '-7 days')`;
|
|
2216
|
-
}
|
|
2217
|
-
function sessionPeriodWhere2(period) {
|
|
2218
|
-
if (period === "today")
|
|
2219
|
-
return `DATE(started_at) = DATE('now')`;
|
|
2220
|
-
return `started_at >= DATE('now', 'weekday 0', '-7 days')`;
|
|
2221
|
-
}
|
|
2222
|
-
function baseRequestTotals(row) {
|
|
2223
|
-
return {
|
|
2224
|
-
...ZERO_REQUEST_TOTALS,
|
|
2225
|
-
...row,
|
|
2226
|
-
sessions: Number(row?.request_sessions ?? row?.sessions ?? 0),
|
|
2227
|
-
request_sessions: Number(row?.request_sessions ?? row?.sessions ?? 0),
|
|
2228
|
-
requests: Number(row?.requests ?? 0),
|
|
2229
|
-
input_tokens: Number(row?.input_tokens ?? 0),
|
|
2230
|
-
output_tokens: Number(row?.output_tokens ?? 0),
|
|
2231
|
-
cache_read_tokens: Number(row?.cache_read_tokens ?? 0),
|
|
2232
|
-
cache_create_tokens: Number(row?.cache_create_tokens ?? 0),
|
|
2233
|
-
cache_create_5m_tokens: Number(row?.cache_create_5m_tokens ?? 0),
|
|
2234
|
-
cache_create_1h_tokens: Number(row?.cache_create_1h_tokens ?? 0),
|
|
2235
|
-
total_tokens: Number(row?.total_tokens ?? 0),
|
|
2236
|
-
cost_usd: Number(row?.cost_usd ?? 0),
|
|
2237
|
-
last_active: row?.last_active ?? null
|
|
2238
|
-
};
|
|
2239
|
-
}
|
|
2240
|
-
function baseSessionTotals(row) {
|
|
2241
|
-
return {
|
|
2242
|
-
sessions: Number(row?.sessions ?? 0),
|
|
2243
|
-
requests: Number(row?.requests ?? 0),
|
|
2244
|
-
total_tokens: Number(row?.total_tokens ?? 0),
|
|
2245
|
-
cost_usd: Number(row?.cost_usd ?? 0),
|
|
2246
|
-
last_active: row?.last_active ?? null
|
|
2247
|
-
};
|
|
2248
|
-
}
|
|
2249
|
-
function latestTimestamp(...values) {
|
|
2250
|
-
return values.filter(Boolean).sort().at(-1) ?? null;
|
|
2251
|
-
}
|
|
2252
|
-
function combineTotals(requests, sessions) {
|
|
2253
|
-
return {
|
|
2254
|
-
sessions: requests.request_sessions + sessions.sessions,
|
|
2255
|
-
requests: requests.requests + sessions.requests,
|
|
2256
|
-
input_tokens: requests.input_tokens,
|
|
2257
|
-
output_tokens: requests.output_tokens,
|
|
2258
|
-
cache_read_tokens: requests.cache_read_tokens,
|
|
2259
|
-
cache_create_tokens: requests.cache_create_tokens,
|
|
2260
|
-
cache_create_5m_tokens: requests.cache_create_5m_tokens,
|
|
2261
|
-
cache_create_1h_tokens: requests.cache_create_1h_tokens,
|
|
2262
|
-
total_tokens: requests.total_tokens + sessions.total_tokens,
|
|
2263
|
-
cost_usd: requests.cost_usd + sessions.cost_usd,
|
|
2264
|
-
last_active: latestTimestamp(requests.last_active, sessions.last_active)
|
|
2265
|
-
};
|
|
2266
|
-
}
|
|
2267
|
-
function emptyTotals() {
|
|
2268
|
-
return combineTotals(ZERO_REQUEST_TOTALS, ZERO_SESSION_TOTALS);
|
|
2269
|
-
}
|
|
2270
|
-
function requestTotalsSql(where, machine) {
|
|
2271
|
-
const params = [];
|
|
2272
|
-
let machineClause = "";
|
|
2273
|
-
if (machine) {
|
|
2274
|
-
machineClause = " AND machine_id = ?";
|
|
2275
|
-
params.push(machine);
|
|
2276
|
-
}
|
|
2277
|
-
return {
|
|
2278
|
-
sql: `
|
|
2279
|
-
SELECT
|
|
2280
|
-
COUNT(DISTINCT session_id) as request_sessions,
|
|
2281
|
-
COUNT(*) as requests,
|
|
2282
|
-
COALESCE(SUM(input_tokens), 0) as input_tokens,
|
|
2283
|
-
COALESCE(SUM(output_tokens), 0) as output_tokens,
|
|
2284
|
-
COALESCE(SUM(cache_read_tokens), 0) as cache_read_tokens,
|
|
2285
|
-
COALESCE(SUM(cache_create_tokens), 0) as cache_create_tokens,
|
|
2286
|
-
COALESCE(SUM(COALESCE(cache_create_5m_tokens, cache_create_tokens)), 0) as cache_create_5m_tokens,
|
|
2287
|
-
COALESCE(SUM(COALESCE(cache_create_1h_tokens, 0)), 0) as cache_create_1h_tokens,
|
|
2288
|
-
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens,
|
|
2289
|
-
COALESCE(SUM(cost_usd), 0) as cost_usd,
|
|
2290
|
-
MAX(timestamp) as last_active
|
|
2291
|
-
FROM requests
|
|
2292
|
-
WHERE ${where}${machineClause}
|
|
2293
|
-
`,
|
|
2294
|
-
params
|
|
2295
|
-
};
|
|
2296
|
-
}
|
|
2297
|
-
function sessionOnlyTotalsSql(where, machine) {
|
|
2298
|
-
const params = [];
|
|
2299
|
-
let machineClause = "";
|
|
2300
|
-
if (machine) {
|
|
2301
|
-
machineClause = " AND machine_id = ?";
|
|
2302
|
-
params.push(machine);
|
|
2303
|
-
}
|
|
2304
|
-
return {
|
|
2305
|
-
sql: `
|
|
2306
|
-
SELECT
|
|
2307
|
-
COUNT(*) as sessions,
|
|
2308
|
-
COALESCE(SUM(request_count), 0) as requests,
|
|
2309
|
-
COALESCE(SUM(total_tokens), 0) as total_tokens,
|
|
2310
|
-
COALESCE(SUM(total_cost_usd), 0) as cost_usd,
|
|
2311
|
-
MAX(started_at) as last_active
|
|
2312
|
-
FROM sessions
|
|
2313
|
-
WHERE ${where}${machineClause}
|
|
2314
|
-
AND NOT EXISTS (SELECT 1 FROM requests r WHERE r.session_id = sessions.id)
|
|
2315
|
-
`,
|
|
2316
|
-
params
|
|
2317
|
-
};
|
|
2318
|
-
}
|
|
2319
|
-
function queryRequestTotals(db, where, params, machine) {
|
|
2320
|
-
const q = requestTotalsSql(where, machine);
|
|
2321
|
-
return baseRequestTotals(db.prepare(q.sql).get(...params, ...q.params));
|
|
2322
|
-
}
|
|
2323
|
-
function querySessionOnlyTotals(db, where, params, machine) {
|
|
2324
|
-
const q = sessionOnlyTotalsSql(where, machine);
|
|
2325
|
-
return baseSessionTotals(db.prepare(q.sql).get(...params, ...q.params));
|
|
2326
|
-
}
|
|
2327
|
-
function queryPeriodTotals(db, period, machine) {
|
|
2328
|
-
const summary = querySummary(db, period, machine);
|
|
2329
|
-
const requests = queryRequestTotals(db, requestPeriodWhere2(period), [], machine);
|
|
2330
|
-
const sessions = querySessionOnlyTotals(db, sessionPeriodWhere2(period), [], machine);
|
|
2331
|
-
const totals = combineTotals(requests, sessions);
|
|
2332
|
-
return {
|
|
2333
|
-
...totals,
|
|
2334
|
-
sessions: summary.sessions,
|
|
2335
|
-
requests: summary.requests,
|
|
2336
|
-
total_tokens: summary.tokens,
|
|
2337
|
-
cost_usd: summary.total_usd
|
|
2338
|
-
};
|
|
2339
|
-
}
|
|
2340
|
-
function querySinceTotals(db, since, machine) {
|
|
2341
|
-
const requests = queryRequestTotals(db, "timestamp >= ?", [since], machine);
|
|
2342
|
-
const sessions = querySessionOnlyTotals(db, "started_at >= ?", [since], machine);
|
|
2343
|
-
return combineTotals(requests, sessions);
|
|
2344
|
-
}
|
|
2345
|
-
function parseSinceDate(input, now) {
|
|
2346
|
-
const value = input.trim();
|
|
2347
|
-
const match = value.match(/^(\d+(?:\.\d+)?)(m|h|d|w)$/i);
|
|
2348
|
-
if (match) {
|
|
2349
|
-
const amount = Number(match[1]);
|
|
2350
|
-
const unit = match[2].toLowerCase();
|
|
2351
|
-
const multiplier = unit === "m" ? 60000 : unit === "h" ? 60 * 60000 : unit === "d" ? 24 * 60 * 60000 : 7 * 24 * 60 * 60000;
|
|
2352
|
-
return {
|
|
2353
|
-
timestamp: new Date(now.getTime() - amount * multiplier).toISOString(),
|
|
2354
|
-
label: value
|
|
2355
|
-
};
|
|
2356
|
-
}
|
|
2357
|
-
const parsed = new Date(value);
|
|
2358
|
-
if (Number.isNaN(parsed.getTime())) {
|
|
2359
|
-
throw new Error("--since must be a duration like 24h, 7d, 30m, 2w, or an ISO date");
|
|
2360
|
-
}
|
|
2361
|
-
return { timestamp: parsed.toISOString(), label: value };
|
|
2362
|
-
}
|
|
2363
|
-
function queryMachineSinceRows(db, since, machine) {
|
|
2364
|
-
const knownMachines = new Set;
|
|
2365
|
-
for (const m of listMachines(db, "all")) {
|
|
2366
|
-
if (m.machine_id)
|
|
2367
|
-
knownMachines.add(m.machine_id);
|
|
2368
|
-
}
|
|
2369
|
-
for (const m of listMachineRegistry(db)) {
|
|
2370
|
-
if (m.machine_id)
|
|
2371
|
-
knownMachines.add(m.machine_id);
|
|
2372
|
-
}
|
|
2373
|
-
if (machine)
|
|
2374
|
-
knownMachines.add(machine);
|
|
2375
|
-
const requestMachineClause = machine ? " AND machine_id = ?" : "";
|
|
2376
|
-
const requestRows = db.prepare(`
|
|
2377
|
-
SELECT
|
|
2378
|
-
machine_id,
|
|
2379
|
-
COUNT(DISTINCT session_id) as request_sessions,
|
|
2380
|
-
COUNT(*) as requests,
|
|
2381
|
-
COALESCE(SUM(input_tokens), 0) as input_tokens,
|
|
2382
|
-
COALESCE(SUM(output_tokens), 0) as output_tokens,
|
|
2383
|
-
COALESCE(SUM(cache_read_tokens), 0) as cache_read_tokens,
|
|
2384
|
-
COALESCE(SUM(cache_create_tokens), 0) as cache_create_tokens,
|
|
2385
|
-
COALESCE(SUM(COALESCE(cache_create_5m_tokens, cache_create_tokens)), 0) as cache_create_5m_tokens,
|
|
2386
|
-
COALESCE(SUM(COALESCE(cache_create_1h_tokens, 0)), 0) as cache_create_1h_tokens,
|
|
2387
|
-
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens,
|
|
2388
|
-
COALESCE(SUM(cost_usd), 0) as cost_usd,
|
|
2389
|
-
MAX(timestamp) as last_active
|
|
2390
|
-
FROM requests
|
|
2391
|
-
WHERE timestamp >= ?
|
|
2392
|
-
AND machine_id != ''
|
|
2393
|
-
${requestMachineClause}
|
|
2394
|
-
GROUP BY machine_id
|
|
2395
|
-
`).all(...machine ? [since, machine] : [since]);
|
|
2396
|
-
const sessionMachineClause = machine ? " AND machine_id = ?" : "";
|
|
2397
|
-
const sessionRows = db.prepare(`
|
|
2398
|
-
SELECT
|
|
2399
|
-
machine_id,
|
|
2400
|
-
COUNT(*) as sessions,
|
|
2401
|
-
COALESCE(SUM(request_count), 0) as requests,
|
|
2402
|
-
COALESCE(SUM(total_tokens), 0) as total_tokens,
|
|
2403
|
-
COALESCE(SUM(total_cost_usd), 0) as cost_usd,
|
|
2404
|
-
MAX(started_at) as last_active
|
|
2405
|
-
FROM sessions
|
|
2406
|
-
WHERE started_at >= ?
|
|
2407
|
-
AND machine_id != ''
|
|
2408
|
-
${sessionMachineClause}
|
|
2409
|
-
AND NOT EXISTS (SELECT 1 FROM requests r WHERE r.session_id = sessions.id)
|
|
2410
|
-
GROUP BY machine_id
|
|
2411
|
-
`).all(...machine ? [since, machine] : [since]);
|
|
2412
|
-
const groups = new Map;
|
|
2413
|
-
for (const row of requestRows) {
|
|
2414
|
-
knownMachines.add(row.machine_id);
|
|
2415
|
-
groups.set(row.machine_id, combineTotals(baseRequestTotals(row), ZERO_SESSION_TOTALS));
|
|
2416
|
-
}
|
|
2417
|
-
for (const row of sessionRows) {
|
|
2418
|
-
knownMachines.add(row.machine_id);
|
|
2419
|
-
const current = groups.get(row.machine_id) ?? emptyTotals();
|
|
2420
|
-
const sessions = baseSessionTotals(row);
|
|
2421
|
-
groups.set(row.machine_id, {
|
|
2422
|
-
...current,
|
|
2423
|
-
sessions: current.sessions + sessions.sessions,
|
|
2424
|
-
requests: current.requests + sessions.requests,
|
|
2425
|
-
total_tokens: current.total_tokens + sessions.total_tokens,
|
|
2426
|
-
cost_usd: current.cost_usd + sessions.cost_usd,
|
|
2427
|
-
last_active: latestTimestamp(current.last_active, sessions.last_active)
|
|
2428
|
-
});
|
|
2429
|
-
}
|
|
2430
|
-
const lastData = queryMachineLastData(db, machine);
|
|
2431
|
-
const rows = [...knownMachines].filter((m) => !machine || m === machine).map((machineId) => {
|
|
2432
|
-
const totals = groups.get(machineId) ?? emptyTotals();
|
|
2433
|
-
const lastDataAt = lastData.get(machineId) ?? totals.last_active;
|
|
2434
|
-
return {
|
|
2435
|
-
machine_id: machineId,
|
|
2436
|
-
...totals,
|
|
2437
|
-
last_data_at: lastDataAt,
|
|
2438
|
-
last_data_age: ""
|
|
2439
|
-
};
|
|
2440
|
-
});
|
|
2441
|
-
return rows.sort((a, b) => b.cost_usd - a.cost_usd || a.machine_id.localeCompare(b.machine_id));
|
|
2442
|
-
}
|
|
2443
|
-
function queryMachineLastData(db, machine) {
|
|
2444
|
-
const filter = machine ? " AND machine_id = ?" : "";
|
|
2445
|
-
const requestRows = db.prepare(`
|
|
2446
|
-
SELECT machine_id, MAX(timestamp) as last_at
|
|
2447
|
-
FROM requests
|
|
2448
|
-
WHERE machine_id != ''${filter}
|
|
2449
|
-
GROUP BY machine_id
|
|
2450
|
-
`).all(...machine ? [machine] : []);
|
|
2451
|
-
const sessionRows = db.prepare(`
|
|
2452
|
-
SELECT machine_id, MAX(started_at) as last_at
|
|
2453
|
-
FROM sessions
|
|
2454
|
-
WHERE machine_id != ''${filter}
|
|
2455
|
-
GROUP BY machine_id
|
|
2456
|
-
`).all(...machine ? [machine] : []);
|
|
2457
|
-
const result = new Map;
|
|
2458
|
-
for (const row of [...requestRows, ...sessionRows]) {
|
|
2459
|
-
result.set(row.machine_id, latestTimestamp(result.get(row.machine_id), row.last_at));
|
|
2460
|
-
}
|
|
2461
|
-
return result;
|
|
2462
|
-
}
|
|
2463
|
-
function queryAgentSinceRows(db, since, machine) {
|
|
2464
|
-
const requestMachineClause = machine ? " AND machine_id = ?" : "";
|
|
2465
|
-
const sessionMachineClause = machine ? " AND machine_id = ?" : "";
|
|
2466
|
-
const rows = db.prepare(`
|
|
2467
|
-
WITH request_rows AS (
|
|
2468
|
-
SELECT
|
|
2469
|
-
agent,
|
|
2470
|
-
session_id,
|
|
2471
|
-
1 as requests,
|
|
2472
|
-
input_tokens,
|
|
2473
|
-
output_tokens,
|
|
2474
|
-
cache_read_tokens,
|
|
2475
|
-
cache_create_tokens,
|
|
2476
|
-
COALESCE(cache_create_5m_tokens, cache_create_tokens) as cache_create_5m_tokens,
|
|
2477
|
-
COALESCE(cache_create_1h_tokens, 0) as cache_create_1h_tokens,
|
|
2478
|
-
input_tokens + output_tokens + cache_read_tokens + cache_create_tokens as total_tokens,
|
|
2479
|
-
cost_usd,
|
|
2480
|
-
timestamp as last_active
|
|
2481
|
-
FROM requests
|
|
2482
|
-
WHERE timestamp >= ?${requestMachineClause}
|
|
2483
|
-
),
|
|
2484
|
-
session_only_rows AS (
|
|
2485
|
-
SELECT
|
|
2486
|
-
agent,
|
|
2487
|
-
id as session_id,
|
|
2488
|
-
COALESCE(request_count, 0) as requests,
|
|
2489
|
-
0 as input_tokens,
|
|
2490
|
-
0 as output_tokens,
|
|
2491
|
-
0 as cache_read_tokens,
|
|
2492
|
-
0 as cache_create_tokens,
|
|
2493
|
-
0 as cache_create_5m_tokens,
|
|
2494
|
-
0 as cache_create_1h_tokens,
|
|
2495
|
-
COALESCE(total_tokens, 0) as total_tokens,
|
|
2496
|
-
COALESCE(total_cost_usd, 0) as cost_usd,
|
|
2497
|
-
started_at as last_active
|
|
2498
|
-
FROM sessions
|
|
2499
|
-
WHERE started_at >= ?${sessionMachineClause}
|
|
2500
|
-
AND NOT EXISTS (SELECT 1 FROM requests r WHERE r.session_id = sessions.id)
|
|
2501
|
-
),
|
|
2502
|
-
combined AS (
|
|
2503
|
-
SELECT * FROM request_rows
|
|
2504
|
-
UNION ALL
|
|
2505
|
-
SELECT * FROM session_only_rows
|
|
2506
|
-
)
|
|
2507
|
-
SELECT
|
|
2508
|
-
agent,
|
|
2509
|
-
COUNT(DISTINCT session_id) as sessions,
|
|
2510
|
-
COALESCE(SUM(requests), 0) as requests,
|
|
2511
|
-
COALESCE(SUM(input_tokens), 0) as input_tokens,
|
|
2512
|
-
COALESCE(SUM(output_tokens), 0) as output_tokens,
|
|
2513
|
-
COALESCE(SUM(cache_read_tokens), 0) as cache_read_tokens,
|
|
2514
|
-
COALESCE(SUM(cache_create_tokens), 0) as cache_create_tokens,
|
|
2515
|
-
COALESCE(SUM(cache_create_5m_tokens), 0) as cache_create_5m_tokens,
|
|
2516
|
-
COALESCE(SUM(cache_create_1h_tokens), 0) as cache_create_1h_tokens,
|
|
2517
|
-
COALESCE(SUM(total_tokens), 0) as total_tokens,
|
|
2518
|
-
COALESCE(SUM(cost_usd), 0) as cost_usd,
|
|
2519
|
-
MAX(last_active) as last_active
|
|
2520
|
-
FROM combined
|
|
2521
|
-
GROUP BY agent
|
|
2522
|
-
ORDER BY cost_usd DESC
|
|
2523
|
-
`).all(...machine ? [since, machine, since, machine] : [since, since]);
|
|
2524
|
-
if (rows.length > 0)
|
|
2525
|
-
return rows;
|
|
2526
|
-
return queryAgentBreakdown(db, "all", machine).filter((row) => row.last_active >= since).map(agentBreakdownToBriefRow);
|
|
2527
|
-
}
|
|
2528
|
-
function agentBreakdownToBriefRow(row) {
|
|
2529
|
-
return {
|
|
2530
|
-
agent: row.agent,
|
|
2531
|
-
sessions: row.sessions,
|
|
2532
|
-
requests: row.requests,
|
|
2533
|
-
input_tokens: 0,
|
|
2534
|
-
output_tokens: 0,
|
|
2535
|
-
cache_read_tokens: 0,
|
|
2536
|
-
cache_create_tokens: 0,
|
|
2537
|
-
cache_create_5m_tokens: 0,
|
|
2538
|
-
cache_create_1h_tokens: 0,
|
|
2539
|
-
total_tokens: row.total_tokens,
|
|
2540
|
-
cost_usd: row.cost_usd,
|
|
2541
|
-
last_active: row.last_active
|
|
2542
|
-
};
|
|
2543
|
-
}
|
|
2544
|
-
function normalizeAccountEmail2(email) {
|
|
2545
|
-
return email.trim().toLowerCase();
|
|
2546
|
-
}
|
|
2547
|
-
function accountKey(row) {
|
|
2548
|
-
const agent = row.agent || row.account_tool || "unknown";
|
|
2549
|
-
const email = normalizeAccountEmail2(row.account_email);
|
|
2550
|
-
if (email)
|
|
2551
|
-
return `${agent}:${email}`;
|
|
2552
|
-
if (row.account_name)
|
|
2553
|
-
return `${agent}:${row.account_name}`;
|
|
2554
|
-
return row.account_key || `${agent}:unknown`;
|
|
2555
|
-
}
|
|
2556
|
-
function addAccountRow(groups, row) {
|
|
2557
|
-
if (!row.account_key && !row.account_tool && !row.account_name && !row.account_email)
|
|
2558
|
-
return;
|
|
2559
|
-
const key = accountKey(row);
|
|
2560
|
-
const email = normalizeAccountEmail2(row.account_email);
|
|
2561
|
-
const current = groups.get(key) ?? {
|
|
2562
|
-
account_key: key,
|
|
2563
|
-
account_tool: row.agent || row.account_tool || "unknown",
|
|
2564
|
-
account_name: row.account_name || email || row.account_key || "unknown",
|
|
2565
|
-
account_email: email || null,
|
|
2566
|
-
account_source: row.account_source || "unknown",
|
|
2567
|
-
sessions: 0,
|
|
2568
|
-
requests: 0,
|
|
2569
|
-
input_tokens: 0,
|
|
2570
|
-
output_tokens: 0,
|
|
2571
|
-
cache_read_tokens: 0,
|
|
2572
|
-
cache_create_tokens: 0,
|
|
2573
|
-
cache_create_5m_tokens: 0,
|
|
2574
|
-
cache_create_1h_tokens: 0,
|
|
2575
|
-
total_tokens: 0,
|
|
2576
|
-
cost_usd: 0,
|
|
2577
|
-
last_active: null
|
|
2578
|
-
};
|
|
2579
|
-
current.sessions += Number(row.sessions ?? 0);
|
|
2580
|
-
if (!current.account_email && email)
|
|
2581
|
-
current.account_email = email;
|
|
2582
|
-
if ((!current.account_name || current.account_name === "unknown") && (row.account_name || email)) {
|
|
2583
|
-
current.account_name = row.account_name || email;
|
|
2584
|
-
}
|
|
2585
|
-
if ((!current.account_source || current.account_source === "unknown") && row.account_source && row.account_source !== "unknown") {
|
|
2586
|
-
current.account_source = row.account_source;
|
|
2587
|
-
}
|
|
2588
|
-
current.requests += Number(row.requests ?? 0);
|
|
2589
|
-
current.input_tokens += Number(row.input_tokens ?? 0);
|
|
2590
|
-
current.output_tokens += Number(row.output_tokens ?? 0);
|
|
2591
|
-
current.cache_read_tokens += Number(row.cache_read_tokens ?? 0);
|
|
2592
|
-
current.cache_create_tokens += Number(row.cache_create_tokens ?? 0);
|
|
2593
|
-
current.cache_create_5m_tokens += Number(row.cache_create_5m_tokens ?? 0);
|
|
2594
|
-
current.cache_create_1h_tokens += Number(row.cache_create_1h_tokens ?? 0);
|
|
2595
|
-
current.total_tokens += Number(row.total_tokens ?? 0);
|
|
2596
|
-
current.cost_usd += Number(row.cost_usd ?? 0);
|
|
2597
|
-
current.last_active = latestTimestamp(current.last_active, row.last_active);
|
|
2598
|
-
groups.set(key, current);
|
|
2599
|
-
}
|
|
2600
|
-
function queryAccountSinceRows(db, since, machine) {
|
|
2601
|
-
const requestMachineClause = machine ? " AND r.machine_id = ?" : "";
|
|
2602
|
-
const sessionMachineClause = machine ? " AND s.machine_id = ?" : "";
|
|
2603
|
-
const requestRows = db.prepare(`
|
|
2604
|
-
SELECT
|
|
2605
|
-
COALESCE(NULLIF(r.agent, ''), NULLIF(s.agent, ''), '') as agent,
|
|
2606
|
-
COALESCE(NULLIF(r.account_key, ''), NULLIF(s.account_key, ''), '') as account_key,
|
|
2607
|
-
COALESCE(NULLIF(r.account_tool, ''), NULLIF(s.account_tool, ''), '') as account_tool,
|
|
2608
|
-
COALESCE(NULLIF(r.account_name, ''), NULLIF(s.account_name, ''), '') as account_name,
|
|
2609
|
-
COALESCE(NULLIF(r.account_email, ''), NULLIF(s.account_email, ''), '') as account_email,
|
|
2610
|
-
COALESCE(NULLIF(r.account_source, ''), NULLIF(s.account_source, ''), 'unknown') as account_source,
|
|
2611
|
-
COUNT(DISTINCT r.session_id) as sessions,
|
|
2612
|
-
COUNT(*) as requests,
|
|
2613
|
-
COALESCE(SUM(r.input_tokens), 0) as input_tokens,
|
|
2614
|
-
COALESCE(SUM(r.output_tokens), 0) as output_tokens,
|
|
2615
|
-
COALESCE(SUM(r.cache_read_tokens), 0) as cache_read_tokens,
|
|
2616
|
-
COALESCE(SUM(r.cache_create_tokens), 0) as cache_create_tokens,
|
|
2617
|
-
COALESCE(SUM(COALESCE(r.cache_create_5m_tokens, r.cache_create_tokens, 0)), 0) as cache_create_5m_tokens,
|
|
2618
|
-
COALESCE(SUM(COALESCE(r.cache_create_1h_tokens, 0)), 0) as cache_create_1h_tokens,
|
|
2619
|
-
COALESCE(SUM(r.input_tokens + r.output_tokens + r.cache_read_tokens + r.cache_create_tokens), 0) as total_tokens,
|
|
2620
|
-
COALESCE(SUM(r.cost_usd), 0) as cost_usd,
|
|
2621
|
-
MAX(r.timestamp) as last_active
|
|
2622
|
-
FROM requests r
|
|
2623
|
-
LEFT JOIN sessions s ON s.id = r.session_id
|
|
2624
|
-
WHERE r.timestamp >= ?${requestMachineClause}
|
|
2625
|
-
GROUP BY
|
|
2626
|
-
COALESCE(NULLIF(r.agent, ''), NULLIF(s.agent, ''), ''),
|
|
2627
|
-
COALESCE(NULLIF(r.account_key, ''), NULLIF(s.account_key, ''), ''),
|
|
2628
|
-
COALESCE(NULLIF(r.account_tool, ''), NULLIF(s.account_tool, ''), ''),
|
|
2629
|
-
COALESCE(NULLIF(r.account_name, ''), NULLIF(s.account_name, ''), ''),
|
|
2630
|
-
COALESCE(NULLIF(r.account_email, ''), NULLIF(s.account_email, ''), ''),
|
|
2631
|
-
COALESCE(NULLIF(r.account_source, ''), NULLIF(s.account_source, ''), 'unknown')
|
|
2632
|
-
`).all(...machine ? [since, machine] : [since]);
|
|
2633
|
-
const sessionRows = db.prepare(`
|
|
2634
|
-
SELECT
|
|
2635
|
-
s.agent as agent,
|
|
2636
|
-
s.account_key as account_key,
|
|
2637
|
-
s.account_tool as account_tool,
|
|
2638
|
-
s.account_name as account_name,
|
|
2639
|
-
COALESCE(s.account_email, '') as account_email,
|
|
2640
|
-
COALESCE(NULLIF(s.account_source, ''), 'unknown') as account_source,
|
|
2641
|
-
COUNT(DISTINCT s.id) as sessions,
|
|
2642
|
-
COALESCE(SUM(s.request_count), 0) as requests,
|
|
2643
|
-
0 as input_tokens,
|
|
2644
|
-
0 as output_tokens,
|
|
2645
|
-
0 as cache_read_tokens,
|
|
2646
|
-
0 as cache_create_tokens,
|
|
2647
|
-
0 as cache_create_5m_tokens,
|
|
2648
|
-
0 as cache_create_1h_tokens,
|
|
2649
|
-
COALESCE(SUM(s.total_tokens), 0) as total_tokens,
|
|
2650
|
-
COALESCE(SUM(s.total_cost_usd), 0) as cost_usd,
|
|
2651
|
-
MAX(s.started_at) as last_active
|
|
2652
|
-
FROM sessions s
|
|
2653
|
-
WHERE s.started_at >= ?${sessionMachineClause}
|
|
2654
|
-
AND NOT EXISTS (SELECT 1 FROM requests r WHERE r.session_id = s.id)
|
|
2655
|
-
GROUP BY
|
|
2656
|
-
s.agent,
|
|
2657
|
-
s.account_key,
|
|
2658
|
-
s.account_tool,
|
|
2659
|
-
s.account_name,
|
|
2660
|
-
COALESCE(s.account_email, ''),
|
|
2661
|
-
COALESCE(NULLIF(s.account_source, ''), 'unknown')
|
|
2662
|
-
`).all(...machine ? [since, machine] : [since]);
|
|
2663
|
-
const groups = new Map;
|
|
2664
|
-
for (const row of requestRows)
|
|
2665
|
-
addAccountRow(groups, row);
|
|
2666
|
-
for (const row of sessionRows)
|
|
2667
|
-
addAccountRow(groups, row);
|
|
2668
|
-
const rows = [...groups.values()];
|
|
2669
|
-
rows.sort((a, b) => b.cost_usd - a.cost_usd || a.account_key.localeCompare(b.account_key));
|
|
2670
|
-
if (rows.length > 0)
|
|
2671
|
-
return rows;
|
|
2672
|
-
return queryAccountBreakdown(db, "all", machine).filter((row) => row.last_active >= since).map(accountBreakdownToBriefRow);
|
|
2673
|
-
}
|
|
2674
|
-
function accountBreakdownToBriefRow(row) {
|
|
2675
|
-
return {
|
|
2676
|
-
account_key: row.account_key,
|
|
2677
|
-
account_tool: row.account_tool,
|
|
2678
|
-
account_name: row.account_name,
|
|
2679
|
-
account_email: row.account_email,
|
|
2680
|
-
account_source: row.account_source,
|
|
2681
|
-
sessions: row.sessions,
|
|
2682
|
-
requests: row.requests,
|
|
2683
|
-
input_tokens: 0,
|
|
2684
|
-
output_tokens: 0,
|
|
2685
|
-
cache_read_tokens: 0,
|
|
2686
|
-
cache_create_tokens: 0,
|
|
2687
|
-
cache_create_5m_tokens: 0,
|
|
2688
|
-
cache_create_1h_tokens: 0,
|
|
2689
|
-
total_tokens: row.total_tokens,
|
|
2690
|
-
cost_usd: row.cost_usd,
|
|
2691
|
-
last_active: row.last_active
|
|
2692
|
-
};
|
|
2693
|
-
}
|
|
2694
|
-
function queryFreshness(db, now, machine, localSync) {
|
|
2695
|
-
const requestFilter = machine ? " AND machine_id = ?" : "";
|
|
2696
|
-
const requestRows = db.prepare(`
|
|
2697
|
-
SELECT machine_id, MAX(timestamp) as max_request_at
|
|
2698
|
-
FROM requests
|
|
2699
|
-
WHERE machine_id != ''${requestFilter}
|
|
2700
|
-
GROUP BY machine_id
|
|
2701
|
-
`).all(...machine ? [machine] : []);
|
|
2702
|
-
const registryRows = listMachineRegistry(db).filter((row) => !machine || row.machine_id === machine);
|
|
2703
|
-
const machines = new Map;
|
|
2704
|
-
for (const row of requestRows) {
|
|
2705
|
-
machines.set(row.machine_id, {
|
|
2706
|
-
machine_id: row.machine_id,
|
|
2707
|
-
max_request_at: row.max_request_at,
|
|
2708
|
-
request_age: formatAge(row.max_request_at, now),
|
|
2709
|
-
last_merge_sync_at: null,
|
|
2710
|
-
merge_sync_age: "never"
|
|
2711
|
-
});
|
|
2712
|
-
}
|
|
2713
|
-
for (const row of registryRows) {
|
|
2714
|
-
const current = machines.get(row.machine_id) ?? {
|
|
2715
|
-
machine_id: row.machine_id,
|
|
2716
|
-
max_request_at: null,
|
|
2717
|
-
request_age: "never",
|
|
2718
|
-
last_merge_sync_at: null,
|
|
2719
|
-
merge_sync_age: "never"
|
|
2720
|
-
};
|
|
2721
|
-
const syncAt = latestTimestamp(row.last_pull_at, row.last_push_at, row.updated_at);
|
|
2722
|
-
current.last_merge_sync_at = syncAt;
|
|
2723
|
-
current.merge_sync_age = formatAge(syncAt, now);
|
|
2724
|
-
machines.set(row.machine_id, current);
|
|
2725
|
-
}
|
|
2726
|
-
if (localSync && (!machine || machine === localSync.machineId)) {
|
|
2727
|
-
const current = machines.get(localSync.machineId) ?? {
|
|
2728
|
-
machine_id: localSync.machineId,
|
|
2729
|
-
max_request_at: null,
|
|
2730
|
-
request_age: "never",
|
|
2731
|
-
last_merge_sync_at: null,
|
|
2732
|
-
merge_sync_age: "never"
|
|
2733
|
-
};
|
|
2734
|
-
current.last_merge_sync_at = latestTimestamp(current.last_merge_sync_at, localSync.syncedAt);
|
|
2735
|
-
current.merge_sync_age = formatAge(current.last_merge_sync_at, now);
|
|
2736
|
-
machines.set(localSync.machineId, current);
|
|
2737
|
-
}
|
|
2738
|
-
const rows = [...machines.values()].sort((a, b) => a.machine_id.localeCompare(b.machine_id));
|
|
2739
|
-
return {
|
|
2740
|
-
machines: rows,
|
|
2741
|
-
max_request_line: rows.length ? rows.map((row) => `${row.machine_id} ${row.max_request_at ?? "never"} (${row.request_age})`).join("; ") : "none",
|
|
2742
|
-
merge_sync_line: rows.length ? rows.map((row) => `${row.machine_id} ${row.last_merge_sync_at ?? "never"} (${row.merge_sync_age})`).join("; ") : "none"
|
|
2743
|
-
};
|
|
2744
|
-
}
|
|
2745
|
-
function buildBrief(db, opts = {}) {
|
|
2746
|
-
const now = opts.now ?? new Date;
|
|
2747
|
-
const machine = machineFilter(opts.machine);
|
|
2748
|
-
const sinceInput = opts.since ?? "24h";
|
|
2749
|
-
const since = parseSinceDate(sinceInput, now);
|
|
2750
|
-
const summaries = [
|
|
2751
|
-
{
|
|
2752
|
-
label: "Today",
|
|
2753
|
-
period: "today",
|
|
2754
|
-
...queryPeriodTotals(db, "today", machine)
|
|
2755
|
-
},
|
|
2756
|
-
{
|
|
2757
|
-
label: "Week",
|
|
2758
|
-
period: "week",
|
|
2759
|
-
...queryPeriodTotals(db, "week", machine)
|
|
2760
|
-
},
|
|
2761
|
-
{
|
|
2762
|
-
label: `Since ${since.label}`,
|
|
2763
|
-
period: "since",
|
|
2764
|
-
since: since.timestamp,
|
|
2765
|
-
...querySinceTotals(db, since.timestamp, machine)
|
|
2766
|
-
}
|
|
2767
|
-
];
|
|
2768
|
-
const machines = queryMachineSinceRows(db, since.timestamp, machine);
|
|
2769
|
-
for (const row of machines) {
|
|
2770
|
-
row.last_data_age = formatAge(row.last_data_at, now);
|
|
2771
|
-
}
|
|
2772
|
-
return {
|
|
2773
|
-
generated_at: now.toISOString(),
|
|
2774
|
-
machine: machine ?? "all",
|
|
2775
|
-
since: {
|
|
2776
|
-
input: sinceInput,
|
|
2777
|
-
timestamp: since.timestamp,
|
|
2778
|
-
label: since.label
|
|
2779
|
-
},
|
|
2780
|
-
summaries,
|
|
2781
|
-
machines,
|
|
2782
|
-
agents: queryAgentSinceRows(db, since.timestamp, machine),
|
|
2783
|
-
accounts: queryAccountSinceRows(db, since.timestamp, machine),
|
|
2784
|
-
freshness: queryFreshness(db, now, machine, opts.currentMachineId && opts.localSyncAt ? { machineId: opts.currentMachineId, syncedAt: opts.localSyncAt.toISOString() } : undefined)
|
|
2785
|
-
};
|
|
2786
|
-
}
|
|
2787
|
-
|
|
2788
|
-
// src/lib/analytics.ts
|
|
2789
|
-
function queryProjectDetail(db, nameOrPath) {
|
|
2790
|
-
const like = `%${nameOrPath}%`;
|
|
2791
|
-
const sessions = db.prepare(`SELECT id, project_name, project_path, total_cost_usd, total_tokens, started_at
|
|
2792
|
-
FROM sessions
|
|
2793
|
-
WHERE project_name LIKE ? OR project_path LIKE ?
|
|
2794
|
-
ORDER BY started_at DESC`).all(like, like);
|
|
2795
|
-
if (sessions.length === 0)
|
|
2796
|
-
return null;
|
|
2797
|
-
const first = sessions[0];
|
|
2798
|
-
const totalCost = sessions.reduce((s, r) => s + (r.total_cost_usd ?? 0), 0);
|
|
2799
|
-
const totalTokens = sessions.reduce((s, r) => s + (r.total_tokens ?? 0), 0);
|
|
2800
|
-
const daily = db.prepare(`SELECT DATE(r.timestamp) as date, COALESCE(SUM(r.cost_usd), 0) as cost_usd
|
|
2801
|
-
FROM requests r JOIN sessions s ON r.session_id = s.id
|
|
2802
|
-
WHERE (s.project_name LIKE ? OR s.project_path LIKE ?)
|
|
2803
|
-
AND r.timestamp >= DATE('now', '-14 days')
|
|
2804
|
-
GROUP BY date ORDER BY date ASC`).all(like, like);
|
|
2805
|
-
const models = db.prepare(`SELECT r.model as model, COUNT(*) as requests, COALESCE(SUM(r.cost_usd), 0) as cost_usd
|
|
2806
|
-
FROM requests r JOIN sessions s ON r.session_id = s.id
|
|
2807
|
-
WHERE s.project_name LIKE ? OR s.project_path LIKE ?
|
|
2808
|
-
GROUP BY r.model ORDER BY cost_usd DESC LIMIT 5`).all(like, like);
|
|
2809
|
-
const top_sessions = [...sessions].sort((a, b) => (b.total_cost_usd ?? 0) - (a.total_cost_usd ?? 0)).slice(0, 5).map((s) => ({ id: s.id, total_cost_usd: s.total_cost_usd ?? 0, started_at: s.started_at }));
|
|
2810
|
-
return {
|
|
2811
|
-
project_name: first.project_name || nameOrPath,
|
|
2812
|
-
project_path: first.project_path || "",
|
|
2813
|
-
total_cost_usd: totalCost,
|
|
2814
|
-
total_tokens: totalTokens,
|
|
2815
|
-
sessions: sessions.length,
|
|
2816
|
-
daily,
|
|
2817
|
-
models,
|
|
2818
|
-
top_sessions
|
|
2819
|
-
};
|
|
2820
|
-
}
|
|
2821
|
-
function exportDateWhere(column, period) {
|
|
2822
|
-
switch (period) {
|
|
2823
|
-
case "today":
|
|
2824
|
-
return `DATE(${column}) = DATE('now')`;
|
|
2825
|
-
case "week":
|
|
2826
|
-
return `${column} >= DATE('now', '-7 days')`;
|
|
2827
|
-
case "all":
|
|
2828
|
-
return "1=1";
|
|
2829
|
-
default:
|
|
2830
|
-
return `${column} >= DATE('now', '-30 days')`;
|
|
2831
|
-
}
|
|
2832
|
-
}
|
|
2833
|
-
function queryExportRows(db, type, period) {
|
|
2834
|
-
if (type === "requests") {
|
|
2835
|
-
const where2 = exportDateWhere("timestamp", period);
|
|
2836
|
-
return db.prepare(`SELECT * FROM requests WHERE ${where2} ORDER BY timestamp ASC`).all();
|
|
2837
|
-
}
|
|
2838
|
-
const where = exportDateWhere("started_at", period);
|
|
2839
|
-
return db.prepare(`SELECT * FROM sessions WHERE ${where} ORDER BY started_at DESC`).all();
|
|
2840
|
-
}
|
|
2841
|
-
function queryRangeStats(db, from, to) {
|
|
2842
|
-
const r = db.prepare(`SELECT COALESCE(SUM(cost_usd),0) as cost, COUNT(*) as requests,
|
|
2843
|
-
COALESCE(SUM(input_tokens+output_tokens+cache_read_tokens+cache_create_tokens),0) as tokens
|
|
2844
|
-
FROM requests WHERE DATE(timestamp) BETWEEN ? AND ?`).get(from, to);
|
|
2845
|
-
const s = db.prepare(`SELECT COUNT(*) as sessions FROM sessions WHERE DATE(started_at) BETWEEN ? AND ?`).get(from, to);
|
|
2846
|
-
return { cost: r.cost, requests: r.requests, tokens: r.tokens, sessions: s.sessions };
|
|
2847
|
-
}
|
|
2848
|
-
function queryForecast(db, now = new Date) {
|
|
2849
|
-
const daysInMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate();
|
|
2850
|
-
const dayOfMonth = now.getDate();
|
|
2851
|
-
const monthStart = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-01`;
|
|
2852
|
-
const monthSoFar = db.prepare(`SELECT COALESCE(SUM(cost_usd),0) as cost FROM requests WHERE DATE(timestamp) >= ?`).get(monthStart);
|
|
2853
|
-
const dailyAvg = dayOfMonth > 0 ? monthSoFar.cost / dayOfMonth : 0;
|
|
2854
|
-
const projected = dailyAvg * daysInMonth;
|
|
2855
|
-
const sevenDaysAgo = new Date(now);
|
|
2856
|
-
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
|
|
2857
|
-
const last7 = db.prepare(`SELECT COALESCE(SUM(cost_usd),0) as cost FROM requests WHERE DATE(timestamp) >= ?`).get(sevenDaysAgo.toISOString().substring(0, 10));
|
|
2858
|
-
const last7DailyAvg = last7.cost / 7;
|
|
2859
|
-
const dailyCosts = db.prepare(`SELECT DATE(timestamp) as date, COALESCE(SUM(cost_usd),0) as cost_usd
|
|
2860
|
-
FROM requests WHERE DATE(timestamp) >= ? GROUP BY date ORDER BY cost_usd ASC`).all(monthStart);
|
|
2861
|
-
return {
|
|
2862
|
-
day_of_month: dayOfMonth,
|
|
2863
|
-
days_in_month: daysInMonth,
|
|
2864
|
-
spent_so_far_usd: monthSoFar.cost,
|
|
2865
|
-
daily_avg_usd: dailyAvg,
|
|
2866
|
-
projected_usd: projected,
|
|
2867
|
-
last7_daily_avg_usd: last7DailyAvg,
|
|
2868
|
-
last7_projected_usd: last7DailyAvg * daysInMonth,
|
|
2869
|
-
cheapest_day: dailyCosts[0] ?? null,
|
|
2870
|
-
most_expensive_day: dailyCosts[dailyCosts.length - 1] ?? null
|
|
2871
|
-
};
|
|
2872
|
-
}
|
|
2873
|
-
function queryModelEfficiency(db) {
|
|
2874
|
-
return db.prepare(`SELECT model,
|
|
2875
|
-
COALESCE(SUM(input_tokens),0) as input,
|
|
2876
|
-
COALESCE(SUM(output_tokens),0) as output,
|
|
2877
|
-
COALESCE(SUM(cache_read_tokens),0) as cache_read,
|
|
2878
|
-
COALESCE(SUM(cache_create_tokens),0) as cache_write,
|
|
2879
|
-
COUNT(*) as requests,
|
|
2880
|
-
COALESCE(SUM(cost_usd),0) as cost
|
|
2881
|
-
FROM requests GROUP BY model ORDER BY cost DESC`).all();
|
|
2882
|
-
}
|
|
2883
|
-
|
|
2884
|
-
// src/lib/contracts-client/mode.ts
|
|
2885
|
-
var DEPRECATED_STORAGE_MODE_ALIASES = ["remote", "hybrid", "self_hosted"];
|
|
2886
|
-
function normalizeStorageMode(value) {
|
|
2887
|
-
const normalized = value.trim().toLowerCase().replace(/-/g, "_");
|
|
2888
|
-
if (normalized === "local")
|
|
2889
|
-
return { mode: "local", deprecatedAlias: null };
|
|
2890
|
-
if (normalized === "cloud")
|
|
2891
|
-
return { mode: "cloud", deprecatedAlias: null };
|
|
2892
|
-
if (DEPRECATED_STORAGE_MODE_ALIASES.includes(normalized)) {
|
|
2893
|
-
return { mode: "cloud", deprecatedAlias: normalized };
|
|
2894
|
-
}
|
|
2895
|
-
throw new Error(`Unknown storage mode: ${value}. Use local or cloud.`);
|
|
2896
|
-
}
|
|
2897
|
-
function envToken(name) {
|
|
2898
|
-
return name.toUpperCase().replace(/-/g, "_");
|
|
2899
|
-
}
|
|
2900
|
-
|
|
2901
|
-
// src/lib/contracts-client/transport.ts
|
|
2902
|
-
function defaultCloudBaseUrl(name) {
|
|
2903
|
-
return `https://${name}.hasna.xyz`;
|
|
2904
|
-
}
|
|
2905
|
-
function clientTransportEnvKeys(name) {
|
|
2906
|
-
const token = envToken(name);
|
|
2907
|
-
return {
|
|
2908
|
-
modeKeys: [
|
|
2909
|
-
`HASNA_${token}_STORAGE_MODE`,
|
|
2910
|
-
`HASNA_${token}_MODE`,
|
|
2911
|
-
`${token}_STORAGE_MODE`,
|
|
2912
|
-
`${token}_MODE`
|
|
2913
|
-
],
|
|
2914
|
-
apiUrlKeys: [`HASNA_${token}_API_URL`, `${token}_API_URL`],
|
|
2915
|
-
apiKeyKeys: [`HASNA_${token}_API_KEY`, `${token}_API_KEY`]
|
|
2916
|
-
};
|
|
2917
|
-
}
|
|
2918
|
-
function firstEnv(env, keys) {
|
|
2919
|
-
for (const key of keys) {
|
|
2920
|
-
const value = env[key]?.trim();
|
|
2921
|
-
if (value)
|
|
2922
|
-
return { key, value };
|
|
2923
|
-
}
|
|
2924
|
-
return null;
|
|
2925
|
-
}
|
|
2926
|
-
function toV1BaseUrl(apiUrl) {
|
|
2927
|
-
const url = new URL(apiUrl);
|
|
2928
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
2929
|
-
throw new Error("API URL must use http or https.");
|
|
2930
|
-
}
|
|
2931
|
-
let path = url.pathname.replace(/\/+$/, "");
|
|
2932
|
-
if (path.endsWith("/v1"))
|
|
2933
|
-
path = path.slice(0, -"/v1".length);
|
|
2934
|
-
url.pathname = `${path}/v1`;
|
|
2935
|
-
url.search = "";
|
|
2936
|
-
url.hash = "";
|
|
2937
|
-
return url.toString().replace(/\/+$/, "");
|
|
2938
|
-
}
|
|
2939
|
-
function resolveClientTransport(name, env = process.env) {
|
|
2940
|
-
const keys = clientTransportEnvKeys(name);
|
|
2941
|
-
const modeHit = firstEnv(env, keys.modeKeys);
|
|
2942
|
-
const urlHit = firstEnv(env, keys.apiUrlKeys);
|
|
2943
|
-
const keyHit = firstEnv(env, keys.apiKeyKeys);
|
|
2944
|
-
let mode = "local";
|
|
2945
|
-
let deprecatedAlias = null;
|
|
2946
|
-
let modeSource = "default";
|
|
2947
|
-
const warnings = [];
|
|
2948
|
-
if (modeHit) {
|
|
2949
|
-
const normalized = normalizeStorageMode(modeHit.value);
|
|
2950
|
-
mode = normalized.mode;
|
|
2951
|
-
deprecatedAlias = normalized.deprecatedAlias;
|
|
2952
|
-
modeSource = modeHit.key;
|
|
2953
|
-
if (deprecatedAlias) {
|
|
2954
|
-
warnings.push(`Deprecated mode '${deprecatedAlias}' from ${modeHit.key} is treated as 'cloud'. Prefer ${keys.modeKeys[0]}=cloud.`);
|
|
2955
|
-
}
|
|
2956
|
-
} else if (urlHit && keyHit) {
|
|
2957
|
-
mode = "cloud";
|
|
2958
|
-
modeSource = `${urlHit.key}+${keyHit.key}`;
|
|
2959
|
-
}
|
|
2960
|
-
if (mode === "local") {
|
|
2961
|
-
return {
|
|
2962
|
-
transport: "local",
|
|
2963
|
-
mode,
|
|
2964
|
-
deprecatedAlias,
|
|
2965
|
-
modeSource,
|
|
2966
|
-
baseUrl: null,
|
|
2967
|
-
apiUrlSource: null,
|
|
2968
|
-
apiKeyPresent: Boolean(keyHit),
|
|
2969
|
-
apiKeySource: keyHit ? keyHit.key : null,
|
|
2970
|
-
misconfigured: false,
|
|
2971
|
-
warning: warnings.length > 0 ? warnings.join(" ") : null
|
|
2972
|
-
};
|
|
2973
|
-
}
|
|
2974
|
-
if (!keyHit) {
|
|
2975
|
-
warnings.push(`${modeSource}=cloud but no API key is set (${keys.apiKeyKeys[0]}). Refusing to route to cloud; using local store. Set ${keys.apiKeyKeys[0]} to enable the cloud client.`);
|
|
2976
|
-
return {
|
|
2977
|
-
transport: "local",
|
|
2978
|
-
mode,
|
|
2979
|
-
deprecatedAlias,
|
|
2980
|
-
modeSource,
|
|
2981
|
-
baseUrl: null,
|
|
2982
|
-
apiUrlSource: null,
|
|
2983
|
-
apiKeyPresent: false,
|
|
2984
|
-
apiKeySource: null,
|
|
2985
|
-
misconfigured: true,
|
|
2986
|
-
warning: warnings.join(" ")
|
|
2987
|
-
};
|
|
2988
|
-
}
|
|
2989
|
-
const rawUrl = urlHit?.value ?? defaultCloudBaseUrl(name);
|
|
2990
|
-
const apiUrlSource = urlHit ? urlHit.key : "default";
|
|
2991
|
-
let baseUrl;
|
|
2992
|
-
try {
|
|
2993
|
-
baseUrl = toV1BaseUrl(rawUrl);
|
|
2994
|
-
} catch (error) {
|
|
2995
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2996
|
-
warnings.push(`Invalid API URL from ${apiUrlSource}: ${message}. Using local store.`);
|
|
2997
|
-
return {
|
|
2998
|
-
transport: "local",
|
|
2999
|
-
mode,
|
|
3000
|
-
deprecatedAlias,
|
|
3001
|
-
modeSource,
|
|
3002
|
-
baseUrl: null,
|
|
3003
|
-
apiUrlSource: null,
|
|
3004
|
-
apiKeyPresent: true,
|
|
3005
|
-
apiKeySource: keyHit.key,
|
|
3006
|
-
misconfigured: true,
|
|
3007
|
-
warning: warnings.join(" ")
|
|
3008
|
-
};
|
|
3009
|
-
}
|
|
3010
|
-
return {
|
|
3011
|
-
transport: "cloud-http",
|
|
3012
|
-
mode,
|
|
3013
|
-
deprecatedAlias,
|
|
3014
|
-
modeSource,
|
|
3015
|
-
baseUrl,
|
|
3016
|
-
apiUrlSource,
|
|
3017
|
-
apiKeyPresent: true,
|
|
3018
|
-
apiKeySource: keyHit.key,
|
|
3019
|
-
misconfigured: false,
|
|
3020
|
-
warning: warnings.length > 0 ? warnings.join(" ") : null
|
|
3021
|
-
};
|
|
3022
|
-
}
|
|
3023
|
-
|
|
3024
|
-
class HasnaHttpError extends Error {
|
|
3025
|
-
status;
|
|
3026
|
-
method;
|
|
3027
|
-
path;
|
|
3028
|
-
body;
|
|
3029
|
-
constructor(method, path, status, body) {
|
|
3030
|
-
super(`Hasna cloud request failed: ${method} ${path} -> ${status}`);
|
|
3031
|
-
this.name = "HasnaHttpError";
|
|
3032
|
-
this.status = status;
|
|
3033
|
-
this.method = method;
|
|
3034
|
-
this.path = path;
|
|
3035
|
-
this.body = body;
|
|
3036
|
-
}
|
|
3037
|
-
}
|
|
3038
|
-
var DEFAULT_RETRY_STATUSES = [408, 425, 429, 500, 502, 503, 504];
|
|
3039
|
-
var IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
3040
|
-
function appendQuery(path, query) {
|
|
3041
|
-
if (!query)
|
|
3042
|
-
return path;
|
|
3043
|
-
const params = query instanceof URLSearchParams ? query : new URLSearchParams;
|
|
3044
|
-
if (!(query instanceof URLSearchParams)) {
|
|
3045
|
-
for (const [key, value] of Object.entries(query)) {
|
|
3046
|
-
if (value === null || value === undefined)
|
|
3047
|
-
continue;
|
|
3048
|
-
if (Array.isArray(value)) {
|
|
3049
|
-
for (const v of value)
|
|
3050
|
-
params.append(key, String(v));
|
|
3051
|
-
} else {
|
|
3052
|
-
params.append(key, String(value));
|
|
3053
|
-
}
|
|
3054
|
-
}
|
|
3055
|
-
}
|
|
3056
|
-
const qs = params.toString();
|
|
3057
|
-
if (!qs)
|
|
3058
|
-
return path;
|
|
3059
|
-
return `${path}${path.includes("?") ? "&" : "?"}${qs}`;
|
|
3060
|
-
}
|
|
3061
|
-
var defaultSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
3062
|
-
function createHasnaHttpTransport(options) {
|
|
3063
|
-
const fetchImpl = options.fetchImpl ?? ((input, init) => fetch(input, init));
|
|
3064
|
-
const base = options.baseUrl.replace(/\/+$/, "");
|
|
3065
|
-
const timeoutMs = options.timeoutMs ?? 30000;
|
|
3066
|
-
const sleep = options.sleepImpl ?? defaultSleep;
|
|
3067
|
-
const defaultRetry = options.retry;
|
|
3068
|
-
function resolveRetry(callRetry) {
|
|
3069
|
-
const chosen = callRetry !== undefined ? callRetry : defaultRetry;
|
|
3070
|
-
if (chosen === false)
|
|
3071
|
-
return null;
|
|
3072
|
-
const r = chosen ?? {};
|
|
3073
|
-
return {
|
|
3074
|
-
retries: r.retries ?? 2,
|
|
3075
|
-
baseDelayMs: r.baseDelayMs ?? 200,
|
|
3076
|
-
maxDelayMs: r.maxDelayMs ?? 2000,
|
|
3077
|
-
retryStatuses: r.retryStatuses ?? [...DEFAULT_RETRY_STATUSES]
|
|
3078
|
-
};
|
|
3079
|
-
}
|
|
3080
|
-
async function once(method, rel, url, body, opts) {
|
|
3081
|
-
const headers = {
|
|
3082
|
-
"x-api-key": options.apiKey,
|
|
3083
|
-
Authorization: `Bearer ${options.apiKey}`,
|
|
3084
|
-
Accept: "application/json",
|
|
3085
|
-
...options.headers ?? {},
|
|
3086
|
-
...opts.headers ?? {}
|
|
3087
|
-
};
|
|
3088
|
-
if (opts.idempotencyKey)
|
|
3089
|
-
headers["Idempotency-Key"] = opts.idempotencyKey;
|
|
3090
|
-
const init = { method, headers };
|
|
3091
|
-
if (body !== undefined) {
|
|
3092
|
-
headers["Content-Type"] = "application/json";
|
|
3093
|
-
init.body = JSON.stringify(body);
|
|
3094
|
-
}
|
|
3095
|
-
const controller = new AbortController;
|
|
3096
|
-
const onAbort = () => controller.abort();
|
|
3097
|
-
if (opts.signal) {
|
|
3098
|
-
if (opts.signal.aborted)
|
|
3099
|
-
controller.abort();
|
|
3100
|
-
else
|
|
3101
|
-
opts.signal.addEventListener("abort", onAbort, { once: true });
|
|
3102
|
-
}
|
|
3103
|
-
const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? timeoutMs);
|
|
3104
|
-
init.signal = controller.signal;
|
|
3105
|
-
let response;
|
|
3106
|
-
try {
|
|
3107
|
-
response = await fetchImpl(url, init);
|
|
3108
|
-
} catch (error) {
|
|
3109
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
3110
|
-
if (opts.signal?.aborted)
|
|
3111
|
-
return { ok: false, retryable: false, error: err };
|
|
3112
|
-
return { ok: false, retryable: true, error: err };
|
|
3113
|
-
} finally {
|
|
3114
|
-
clearTimeout(timer);
|
|
3115
|
-
if (opts.signal)
|
|
3116
|
-
opts.signal.removeEventListener("abort", onAbort);
|
|
3117
|
-
}
|
|
3118
|
-
const text = await response.text();
|
|
3119
|
-
let parsed = undefined;
|
|
3120
|
-
if (text.length > 0) {
|
|
3121
|
-
try {
|
|
3122
|
-
parsed = JSON.parse(text);
|
|
3123
|
-
} catch {
|
|
3124
|
-
parsed = text;
|
|
3125
|
-
}
|
|
3126
|
-
}
|
|
3127
|
-
if (!response.ok) {
|
|
3128
|
-
const retry = resolveRetry(opts.retry);
|
|
3129
|
-
const retryable = retry ? retry.retryStatuses.includes(response.status) : false;
|
|
3130
|
-
return { ok: false, retryable, error: new HasnaHttpError(method, rel, response.status, parsed) };
|
|
3131
|
-
}
|
|
3132
|
-
return { ok: true, value: parsed };
|
|
3133
|
-
}
|
|
3134
|
-
async function request(method, path, body, opts = {}) {
|
|
3135
|
-
const upper = method.toUpperCase();
|
|
3136
|
-
const rel = appendQuery(path.startsWith("/") ? path : `/${path}`, opts.query);
|
|
3137
|
-
const url = `${base}${rel}`;
|
|
3138
|
-
const retry = resolveRetry(opts.retry);
|
|
3139
|
-
const methodRetryable = IDEMPOTENT_METHODS.has(upper) || Boolean(opts.idempotencyKey);
|
|
3140
|
-
const maxAttempts = retry && methodRetryable ? retry.retries + 1 : 1;
|
|
3141
|
-
let last = null;
|
|
3142
|
-
for (let attempt = 1;attempt <= maxAttempts; attempt++) {
|
|
3143
|
-
const result = await once(upper, rel, url, body, opts);
|
|
3144
|
-
if (result.ok)
|
|
3145
|
-
return result.value;
|
|
3146
|
-
last = result;
|
|
3147
|
-
const canRetry = retry !== null && methodRetryable && result.retryable && attempt < maxAttempts;
|
|
3148
|
-
if (!canRetry)
|
|
3149
|
-
break;
|
|
3150
|
-
const backoff = Math.min(retry.maxDelayMs, retry.baseDelayMs * 2 ** (attempt - 1));
|
|
3151
|
-
const jitter = Math.floor(Math.random() * (backoff / 2 + 1));
|
|
3152
|
-
await sleep(backoff + jitter);
|
|
3153
|
-
}
|
|
3154
|
-
throw last.error;
|
|
3155
|
-
}
|
|
3156
|
-
return {
|
|
3157
|
-
baseUrl: base,
|
|
3158
|
-
request,
|
|
3159
|
-
get: (path, opts) => request("GET", path, undefined, opts),
|
|
3160
|
-
post: (path, body, opts) => request("POST", path, body, opts),
|
|
3161
|
-
put: (path, body, opts) => request("PUT", path, body, opts),
|
|
3162
|
-
patch: (path, body, opts) => request("PATCH", path, body, opts),
|
|
3163
|
-
del: (path, body, opts) => request("DELETE", path, body, opts)
|
|
3164
|
-
};
|
|
3165
|
-
}
|
|
3166
|
-
function createClientTransport(name, env = process.env, overrides) {
|
|
3167
|
-
const resolution = resolveClientTransport(name, env);
|
|
3168
|
-
if (resolution.misconfigured) {
|
|
3169
|
-
throw new Error(resolution.warning ?? `Client for '${name}' is misconfigured for cloud mode.`);
|
|
3170
|
-
}
|
|
3171
|
-
if (resolution.transport === "local" || !resolution.baseUrl) {
|
|
3172
|
-
return { transport: "local", client: null, resolution };
|
|
3173
|
-
}
|
|
3174
|
-
const keys = clientTransportEnvKeys(name);
|
|
3175
|
-
const apiKey = firstEnv(env, keys.apiKeyKeys)?.value;
|
|
3176
|
-
if (!apiKey) {
|
|
3177
|
-
throw new Error(`Client for '${name}' resolved to cloud-http without an API key.`);
|
|
3178
|
-
}
|
|
3179
|
-
return {
|
|
3180
|
-
transport: "cloud-http",
|
|
3181
|
-
client: createHasnaHttpTransport({
|
|
3182
|
-
name,
|
|
3183
|
-
baseUrl: resolution.baseUrl,
|
|
3184
|
-
apiKey,
|
|
3185
|
-
...overrides?.fetchImpl ? { fetchImpl: overrides.fetchImpl } : {},
|
|
3186
|
-
...overrides?.headers ? { headers: overrides.headers } : {},
|
|
3187
|
-
...overrides?.timeoutMs ? { timeoutMs: overrides.timeoutMs } : {},
|
|
3188
|
-
...overrides?.retry !== undefined ? { retry: overrides.retry } : {},
|
|
3189
|
-
...overrides?.sleepImpl ? { sleepImpl: overrides.sleepImpl } : {}
|
|
3190
|
-
}),
|
|
3191
|
-
resolution
|
|
3192
|
-
};
|
|
3193
|
-
}
|
|
3194
|
-
|
|
3195
|
-
// src/lib/contracts-client/storage.ts
|
|
3196
|
-
function resourcePath(resource) {
|
|
3197
|
-
const trimmed = resource.replace(/^\/+|\/+$/g, "");
|
|
3198
|
-
if (!trimmed)
|
|
3199
|
-
throw new Error("resource must be a non-empty path segment");
|
|
3200
|
-
return `/${trimmed}`;
|
|
3201
|
-
}
|
|
3202
|
-
function entityPath(resource, id) {
|
|
3203
|
-
if (id === undefined || id === null || `${id}`.length === 0) {
|
|
3204
|
-
throw new Error("id must be a non-empty string");
|
|
3205
|
-
}
|
|
3206
|
-
return `${resourcePath(resource)}/${encodeURIComponent(String(id))}`;
|
|
3207
|
-
}
|
|
3208
|
-
function newIdempotencyKey() {
|
|
3209
|
-
const g = globalThis;
|
|
3210
|
-
if (g.crypto?.randomUUID)
|
|
3211
|
-
return g.crypto.randomUUID();
|
|
3212
|
-
return `idmp_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 12)}`;
|
|
3213
|
-
}
|
|
3214
|
-
function extractItems(raw) {
|
|
3215
|
-
if (Array.isArray(raw))
|
|
3216
|
-
return raw;
|
|
3217
|
-
if (raw && typeof raw === "object") {
|
|
3218
|
-
const obj = raw;
|
|
3219
|
-
for (const key of ["items", "data", "results", "rows", "records"]) {
|
|
3220
|
-
if (Array.isArray(obj[key]))
|
|
3221
|
-
return obj[key];
|
|
3222
|
-
}
|
|
3223
|
-
}
|
|
3224
|
-
return [];
|
|
3225
|
-
}
|
|
3226
|
-
function extractTotal(raw) {
|
|
3227
|
-
if (raw && typeof raw === "object") {
|
|
3228
|
-
const obj = raw;
|
|
3229
|
-
for (const key of ["total", "count", "totalCount", "total_count"]) {
|
|
3230
|
-
if (typeof obj[key] === "number")
|
|
3231
|
-
return obj[key];
|
|
3232
|
-
}
|
|
3233
|
-
}
|
|
3234
|
-
return null;
|
|
3235
|
-
}
|
|
3236
|
-
function extractCursor(raw) {
|
|
3237
|
-
if (raw && typeof raw === "object") {
|
|
3238
|
-
const obj = raw;
|
|
3239
|
-
for (const key of ["cursor", "nextCursor", "next_cursor", "next"]) {
|
|
3240
|
-
if (typeof obj[key] === "string")
|
|
3241
|
-
return obj[key];
|
|
3242
|
-
}
|
|
3243
|
-
}
|
|
3244
|
-
return null;
|
|
3245
|
-
}
|
|
3246
|
-
function createHasnaStorageClient(name, transport) {
|
|
3247
|
-
return {
|
|
3248
|
-
name,
|
|
3249
|
-
baseUrl: transport.baseUrl,
|
|
3250
|
-
transport,
|
|
3251
|
-
async list(resource, options = {}) {
|
|
3252
|
-
const raw = await transport.get(resourcePath(resource), options);
|
|
3253
|
-
return {
|
|
3254
|
-
items: extractItems(raw),
|
|
3255
|
-
total: extractTotal(raw),
|
|
3256
|
-
cursor: extractCursor(raw),
|
|
3257
|
-
raw
|
|
3258
|
-
};
|
|
3259
|
-
},
|
|
3260
|
-
async get(resource, id, options = {}) {
|
|
3261
|
-
try {
|
|
3262
|
-
return await transport.get(entityPath(resource, id), options);
|
|
3263
|
-
} catch (error) {
|
|
3264
|
-
if (error instanceof HasnaHttpError && error.status === 404)
|
|
3265
|
-
return null;
|
|
3266
|
-
throw error;
|
|
3267
|
-
}
|
|
3268
|
-
},
|
|
3269
|
-
async create(resource, body, options = {}) {
|
|
3270
|
-
const { idempotencyKey, ...rest } = options;
|
|
3271
|
-
return transport.post(resourcePath(resource), body, {
|
|
3272
|
-
...rest,
|
|
3273
|
-
idempotencyKey: idempotencyKey ?? newIdempotencyKey()
|
|
3274
|
-
});
|
|
3275
|
-
},
|
|
3276
|
-
async update(resource, id, patch, options = {}) {
|
|
3277
|
-
const { method = "PATCH", idempotencyKey, ...rest } = options;
|
|
3278
|
-
const call = method === "PUT" ? transport.put : transport.patch;
|
|
3279
|
-
return call(entityPath(resource, id), patch, { ...rest, ...idempotencyKey ? { idempotencyKey } : {} });
|
|
3280
|
-
},
|
|
3281
|
-
async delete(resource, id, options = {}) {
|
|
3282
|
-
try {
|
|
3283
|
-
await transport.del(entityPath(resource, id), undefined, options);
|
|
3284
|
-
} catch (error) {
|
|
3285
|
-
if (error instanceof HasnaHttpError && error.status === 404)
|
|
3286
|
-
return;
|
|
3287
|
-
throw error;
|
|
3288
|
-
}
|
|
3289
|
-
}
|
|
3290
|
-
};
|
|
3291
|
-
}
|
|
3292
|
-
function resolveStorageClient(name, env = process.env, overrides) {
|
|
3293
|
-
const wired = createClientTransport(name, env, overrides);
|
|
3294
|
-
if (wired.transport === "cloud-http") {
|
|
3295
|
-
return { transport: "cloud-http", client: createHasnaStorageClient(name, wired.client) };
|
|
3296
|
-
}
|
|
3297
|
-
return { transport: "local", client: null };
|
|
3298
|
-
}
|
|
3299
|
-
|
|
3300
|
-
// src/lib/cloud-storage.ts
|
|
3301
|
-
var ECONOMY_APP = "economy";
|
|
3302
|
-
var cache;
|
|
3303
|
-
function resolveEconomyCloudStorage(env = process.env, overrides) {
|
|
3304
|
-
const resolved = resolveStorageClient(ECONOMY_APP, env, overrides);
|
|
3305
|
-
return resolved.transport === "cloud-http" ? { active: true, client: resolved.client } : { active: false, client: null };
|
|
3306
|
-
}
|
|
3307
|
-
function economyCloudStorage(env = process.env) {
|
|
3308
|
-
if (cache === undefined)
|
|
3309
|
-
cache = resolveEconomyCloudStorage(env);
|
|
3310
|
-
return cache;
|
|
3311
|
-
}
|
|
3312
|
-
function cleanQuery(query) {
|
|
3313
|
-
if (!query)
|
|
3314
|
-
return;
|
|
3315
|
-
const out = {};
|
|
3316
|
-
for (const [k, v] of Object.entries(query)) {
|
|
3317
|
-
if (v !== undefined && v !== null && v !== "")
|
|
3318
|
-
out[k] = v;
|
|
3319
|
-
}
|
|
3320
|
-
return Object.keys(out).length > 0 ? out : undefined;
|
|
3321
|
-
}
|
|
3322
|
-
async function cloudListItems(storage, resource, query) {
|
|
3323
|
-
const cleaned = cleanQuery(query);
|
|
3324
|
-
const res = await storage.client.list(resource, cleaned ? { query: cleaned } : {});
|
|
3325
|
-
return res.items;
|
|
3326
|
-
}
|
|
3327
|
-
async function cloudObject(storage, path, query) {
|
|
3328
|
-
const cleaned = cleanQuery(query);
|
|
3329
|
-
const raw = await storage.client.transport.get(path, cleaned ? { query: cleaned } : {});
|
|
3330
|
-
if (raw && typeof raw === "object" && "data" in raw) {
|
|
3331
|
-
return raw.data;
|
|
3332
|
-
}
|
|
3333
|
-
return raw;
|
|
3334
|
-
}
|
|
3335
|
-
|
|
3336
|
-
// src/lib/store/index.ts
|
|
3337
|
-
class LocalStore {
|
|
3338
|
-
transport = "local";
|
|
3339
|
-
_db;
|
|
3340
|
-
db() {
|
|
3341
|
-
if (!this._db) {
|
|
3342
|
-
this._db = openDatabase();
|
|
3343
|
-
ensurePricingSeeded(this._db);
|
|
3344
|
-
}
|
|
3345
|
-
return this._db;
|
|
3346
|
-
}
|
|
3347
|
-
async summary(period, machine) {
|
|
3348
|
-
return querySummary(this.db(), period, machine);
|
|
3349
|
-
}
|
|
3350
|
-
async sessions(filter) {
|
|
3351
|
-
return querySessions(this.db(), filter);
|
|
3352
|
-
}
|
|
3353
|
-
async topSessions(n, agent, since) {
|
|
3354
|
-
return queryTopSessions(this.db(), n, agent, since);
|
|
3355
|
-
}
|
|
3356
|
-
async sessionDetail(id) {
|
|
3357
|
-
return getSessionDetail(this.db(), id);
|
|
3358
|
-
}
|
|
3359
|
-
async modelBreakdown(query = {}) {
|
|
3360
|
-
return query.since ? queryModelBreakdownSince(this.db(), query.since) : queryModelBreakdown(this.db());
|
|
3361
|
-
}
|
|
3362
|
-
async agentBreakdown(query = {}) {
|
|
3363
|
-
return query.since ? queryAgentBreakdownSince(this.db(), query.since) : queryAgentBreakdown(this.db(), query.period ?? "all", query.machine);
|
|
3364
|
-
}
|
|
3365
|
-
async projectBreakdown(query = {}) {
|
|
3366
|
-
return query.since ? queryProjectBreakdownSince(this.db(), query.since) : queryProjectBreakdown(this.db(), query.period ?? "all", query.machine);
|
|
3367
|
-
}
|
|
3368
|
-
async accountBreakdown(query = {}) {
|
|
3369
|
-
return query.since ? queryAccountBreakdownSince(this.db(), query.since) : queryAccountBreakdown(this.db(), query.period ?? "all", query.machine);
|
|
3370
|
-
}
|
|
3371
|
-
async costCenterBreakdown(query = {}) {
|
|
3372
|
-
return queryCostCenterBreakdown(this.db(), query.period ?? "all", {
|
|
3373
|
-
kind: query.kind,
|
|
3374
|
-
machine: query.machine,
|
|
3375
|
-
since: query.since
|
|
3376
|
-
});
|
|
3377
|
-
}
|
|
3378
|
-
async accounts(period) {
|
|
3379
|
-
return queryAccountBreakdown(this.db(), period);
|
|
3380
|
-
}
|
|
3381
|
-
async daily(days, machine) {
|
|
3382
|
-
return queryDailyBreakdown(this.db(), days, machine);
|
|
3383
|
-
}
|
|
3384
|
-
async machines() {
|
|
3385
|
-
return listMachines(this.db());
|
|
3386
|
-
}
|
|
3387
|
-
async fleet(period) {
|
|
3388
|
-
return {
|
|
3389
|
-
summary: querySummary(this.db(), period, undefined, true),
|
|
3390
|
-
machines: listMachines(this.db(), period),
|
|
3391
|
-
registry: listMachineRegistry(this.db())
|
|
3392
|
-
};
|
|
3393
|
-
}
|
|
3394
|
-
async billingSummary(period) {
|
|
3395
|
-
return queryBillingSummary(this.db(), period);
|
|
3396
|
-
}
|
|
3397
|
-
async billingDiff(period) {
|
|
3398
|
-
return queryBillingDiff(this.db(), period);
|
|
3399
|
-
}
|
|
3400
|
-
async usage(period, agent) {
|
|
3401
|
-
const snapshots = queryUsageSnapshots(this.db(), { agent, ...usageSnapshotFilterForPeriod(period) });
|
|
3402
|
-
const summary = querySummary(this.db(), period, undefined, true, agent);
|
|
3403
|
-
return { snapshots, summary };
|
|
3404
|
-
}
|
|
3405
|
-
async savings(period, agent) {
|
|
3406
|
-
return querySavingsSummary(this.db(), period, agent);
|
|
3407
|
-
}
|
|
3408
|
-
async listBudgets() {
|
|
3409
|
-
return getBudgetStatuses(this.db());
|
|
3410
|
-
}
|
|
3411
|
-
async listGoals() {
|
|
3412
|
-
return getGoalStatuses(this.db());
|
|
3413
|
-
}
|
|
3414
|
-
async listPricing() {
|
|
3415
|
-
return listModelPricing(this.db());
|
|
3416
|
-
}
|
|
3417
|
-
async listSubscriptions() {
|
|
3418
|
-
return listSubscriptions(this.db());
|
|
3419
|
-
}
|
|
3420
|
-
async listProjects() {
|
|
3421
|
-
return queryProjectBreakdown(this.db());
|
|
3422
|
-
}
|
|
3423
|
-
async brief(query = {}) {
|
|
3424
|
-
return buildBrief(this.db(), {
|
|
3425
|
-
since: query.since,
|
|
3426
|
-
machine: query.machine,
|
|
3427
|
-
currentMachineId: query.currentMachineId,
|
|
3428
|
-
localSyncAt: query.localSyncAt
|
|
3429
|
-
});
|
|
3430
|
-
}
|
|
3431
|
-
async projectDetail(nameOrPath) {
|
|
3432
|
-
return queryProjectDetail(this.db(), nameOrPath);
|
|
3433
|
-
}
|
|
3434
|
-
async exportRows(type, period) {
|
|
3435
|
-
return queryExportRows(this.db(), type, period);
|
|
3436
|
-
}
|
|
3437
|
-
async rangeStats(from, to) {
|
|
3438
|
-
return queryRangeStats(this.db(), from, to);
|
|
3439
|
-
}
|
|
3440
|
-
async forecast() {
|
|
3441
|
-
return queryForecast(this.db());
|
|
3442
|
-
}
|
|
3443
|
-
async efficiency() {
|
|
3444
|
-
return queryModelEfficiency(this.db());
|
|
3445
|
-
}
|
|
3446
|
-
async recentRequests(since) {
|
|
3447
|
-
return queryRequestsSince(this.db(), since);
|
|
3448
|
-
}
|
|
3449
|
-
async estimate(input) {
|
|
3450
|
-
return estimateCostFromRows(listModelPricing(this.db()), input.model, input.inputTokens, input.outputTokens, input.cacheReadTokens ?? 0, input.cacheWriteTokens ?? 0, input.cacheWrite1hTokens ?? 0, input.cacheStorageTokenHours ?? 0);
|
|
3451
|
-
}
|
|
3452
|
-
async setBudget(input) {
|
|
3453
|
-
const now = new Date().toISOString();
|
|
3454
|
-
const budget = { id: randomUUID(), ...input, created_at: now, updated_at: now };
|
|
3455
|
-
upsertBudget(this.db(), budget);
|
|
3456
|
-
return budget.id;
|
|
3457
|
-
}
|
|
3458
|
-
async removeBudget(id) {
|
|
3459
|
-
deleteBudget(this.db(), id);
|
|
3460
|
-
}
|
|
3461
|
-
async setGoal(input) {
|
|
3462
|
-
const now = new Date().toISOString();
|
|
3463
|
-
upsertGoal(this.db(), { id: randomUUID(), ...input, created_at: now, updated_at: now });
|
|
3464
|
-
}
|
|
3465
|
-
async removeGoal(id) {
|
|
3466
|
-
deleteGoal(this.db(), id);
|
|
3467
|
-
}
|
|
3468
|
-
async setPricing(input) {
|
|
3469
|
-
upsertModelPricing(this.db(), { ...input, updated_at: new Date().toISOString() });
|
|
3470
|
-
}
|
|
3471
|
-
async removePricing(model) {
|
|
3472
|
-
deleteModelPricing(this.db(), model);
|
|
3473
|
-
}
|
|
3474
|
-
async setSubscription(input) {
|
|
3475
|
-
const now = new Date().toISOString();
|
|
3476
|
-
const row = {
|
|
3477
|
-
id: input.id ?? randomUUID(),
|
|
3478
|
-
provider: input.provider,
|
|
3479
|
-
plan: input.plan,
|
|
3480
|
-
agent: input.agent,
|
|
3481
|
-
monthly_fee_usd: input.monthly_fee_usd,
|
|
3482
|
-
included_usage_usd: input.included_usage_usd,
|
|
3483
|
-
billing_cycle_start: input.billing_cycle_start,
|
|
3484
|
-
reset_policy: input.reset_policy,
|
|
3485
|
-
active: input.active,
|
|
3486
|
-
created_at: now,
|
|
3487
|
-
updated_at: now
|
|
3488
|
-
};
|
|
3489
|
-
upsertSubscription(this.db(), row);
|
|
3490
|
-
return row;
|
|
3491
|
-
}
|
|
3492
|
-
async removeSubscription(id) {
|
|
3493
|
-
deleteSubscription(this.db(), id);
|
|
3494
|
-
}
|
|
3495
|
-
async addProject(path, name) {
|
|
3496
|
-
upsertProject(this.db(), {
|
|
3497
|
-
id: randomUUID(),
|
|
3498
|
-
path,
|
|
3499
|
-
name,
|
|
3500
|
-
description: null,
|
|
3501
|
-
tags: [],
|
|
3502
|
-
created_at: new Date().toISOString()
|
|
3503
|
-
});
|
|
3504
|
-
}
|
|
3505
|
-
async renameProject(path, name) {
|
|
3506
|
-
const existing = getProject(this.db(), path);
|
|
3507
|
-
if (!existing)
|
|
3508
|
-
throw new Error("Project not found");
|
|
3509
|
-
upsertProject(this.db(), { ...existing, name });
|
|
3510
|
-
}
|
|
3511
|
-
async removeProject(path) {
|
|
3512
|
-
deleteProject(this.db(), path);
|
|
3513
|
-
}
|
|
3514
|
-
async sendFeedback(input) {
|
|
3515
|
-
insertFeedback(this.db(), {
|
|
3516
|
-
message: input.message,
|
|
3517
|
-
email: input.email ?? null,
|
|
3518
|
-
category: input.category ?? "general",
|
|
3519
|
-
version: packageMetadata.version,
|
|
3520
|
-
machine_id: getMachineId()
|
|
3521
|
-
});
|
|
3522
|
-
}
|
|
3523
|
-
}
|
|
3524
|
-
function q(params) {
|
|
3525
|
-
const out = {};
|
|
3526
|
-
for (const [k, v] of Object.entries(params))
|
|
3527
|
-
if (v !== undefined && v !== null && v !== "")
|
|
3528
|
-
out[k] = v;
|
|
3529
|
-
return out;
|
|
3530
|
-
}
|
|
3531
|
-
|
|
3532
|
-
class ApiStore {
|
|
3533
|
-
cloud;
|
|
3534
|
-
transport = "cloud-http";
|
|
3535
|
-
constructor(cloud) {
|
|
3536
|
-
this.cloud = cloud;
|
|
3537
|
-
}
|
|
3538
|
-
async summary(period, machine) {
|
|
3539
|
-
return cloudObject(this.cloud, "/summary", q({ period, machine }));
|
|
3540
|
-
}
|
|
3541
|
-
async sessions(filter) {
|
|
3542
|
-
return cloudListItems(this.cloud, "sessions", q({ ...filter }));
|
|
3543
|
-
}
|
|
3544
|
-
async topSessions(n, agent, since) {
|
|
3545
|
-
return cloudListItems(this.cloud, "top", q({ n, agent, since }));
|
|
3546
|
-
}
|
|
3547
|
-
async sessionDetail(id) {
|
|
3548
|
-
const matches = await cloudListItems(this.cloud, "sessions", q({ search: id, limit: 1 }));
|
|
3549
|
-
const session = matches[0];
|
|
3550
|
-
if (!session)
|
|
3551
|
-
return null;
|
|
3552
|
-
const requests = await cloudObject(this.cloud, `/sessions/${encodeURIComponent(String(session["id"]))}/requests`);
|
|
3553
|
-
return { session, requests: requests ?? [] };
|
|
3554
|
-
}
|
|
3555
|
-
async modelBreakdown(query = {}) {
|
|
3556
|
-
return cloudListItems(this.cloud, "breakdown", q({ by: "model", period: query.period, since: query.since }));
|
|
3557
|
-
}
|
|
3558
|
-
async agentBreakdown(query = {}) {
|
|
3559
|
-
return cloudListItems(this.cloud, "breakdown", q({ by: "agent", period: query.period, since: query.since }));
|
|
3560
|
-
}
|
|
3561
|
-
async projectBreakdown(query = {}) {
|
|
3562
|
-
return cloudListItems(this.cloud, "breakdown", q({ by: "project", period: query.period, since: query.since }));
|
|
3563
|
-
}
|
|
3564
|
-
async accountBreakdown(query = {}) {
|
|
3565
|
-
return cloudListItems(this.cloud, "breakdown", q({ by: "account", period: query.period, since: query.since }));
|
|
3566
|
-
}
|
|
3567
|
-
async costCenterBreakdown(query = {}) {
|
|
3568
|
-
const rows = await cloudListItems(this.cloud, "breakdown", q({
|
|
3569
|
-
by: query.kind ?? "cost-center",
|
|
3570
|
-
period: query.period,
|
|
3571
|
-
since: query.since
|
|
3572
|
-
}));
|
|
3573
|
-
return rows.filter((row) => Boolean(row?.cost_center_id));
|
|
3574
|
-
}
|
|
3575
|
-
async accounts(period) {
|
|
3576
|
-
return cloudListItems(this.cloud, "accounts", q({ period }));
|
|
3577
|
-
}
|
|
3578
|
-
async daily(days, machine) {
|
|
3579
|
-
return cloudListItems(this.cloud, "daily", q({ days, machine }));
|
|
3580
|
-
}
|
|
3581
|
-
async machines() {
|
|
3582
|
-
return cloudListItems(this.cloud, "machines");
|
|
3583
|
-
}
|
|
3584
|
-
async fleet(period) {
|
|
3585
|
-
const data = await cloudObject(this.cloud, "/fleet", q({ period }));
|
|
3586
|
-
return {
|
|
3587
|
-
summary: data.summary,
|
|
3588
|
-
machines: data.machines ?? [],
|
|
3589
|
-
registry: data.registry ?? []
|
|
3590
|
-
};
|
|
3591
|
-
}
|
|
3592
|
-
async billingSummary(period) {
|
|
3593
|
-
return cloudObject(this.cloud, "/billing", q({ period }));
|
|
3594
|
-
}
|
|
3595
|
-
async billingDiff(period) {
|
|
3596
|
-
return cloudObject(this.cloud, "/billing/diff", q({ period }));
|
|
3597
|
-
}
|
|
3598
|
-
async usage(period, agent) {
|
|
3599
|
-
return cloudObject(this.cloud, "/usage", q({ period, agent }));
|
|
3600
|
-
}
|
|
3601
|
-
async savings(period, agent) {
|
|
3602
|
-
return cloudObject(this.cloud, "/savings", q({ period, agent }));
|
|
3603
|
-
}
|
|
3604
|
-
async listBudgets() {
|
|
3605
|
-
return cloudListItems(this.cloud, "budgets");
|
|
3606
|
-
}
|
|
3607
|
-
async listGoals() {
|
|
3608
|
-
return cloudListItems(this.cloud, "goals");
|
|
3609
|
-
}
|
|
3610
|
-
async listPricing() {
|
|
3611
|
-
return cloudListItems(this.cloud, "pricing");
|
|
3612
|
-
}
|
|
3613
|
-
async listSubscriptions() {
|
|
3614
|
-
return cloudListItems(this.cloud, "subscriptions");
|
|
3615
|
-
}
|
|
3616
|
-
async listProjects() {
|
|
3617
|
-
return cloudListItems(this.cloud, "projects");
|
|
3618
|
-
}
|
|
3619
|
-
async brief(query = {}) {
|
|
3620
|
-
return cloudObject(this.cloud, "/brief", q({ since: query.since, machine: query.machine }));
|
|
3621
|
-
}
|
|
3622
|
-
async projectDetail(nameOrPath) {
|
|
3623
|
-
return cloudObject(this.cloud, "/projects/detail", q({ q: nameOrPath }));
|
|
3624
|
-
}
|
|
3625
|
-
async exportRows(type, period) {
|
|
3626
|
-
return cloudListItems(this.cloud, "export", q({ type, period }));
|
|
3627
|
-
}
|
|
3628
|
-
async rangeStats(from, to) {
|
|
3629
|
-
return cloudObject(this.cloud, "/compare", q({ from, to }));
|
|
3630
|
-
}
|
|
3631
|
-
async forecast() {
|
|
3632
|
-
return cloudObject(this.cloud, "/forecast");
|
|
3633
|
-
}
|
|
3634
|
-
async efficiency() {
|
|
3635
|
-
return cloudListItems(this.cloud, "efficiency");
|
|
3636
|
-
}
|
|
3637
|
-
async recentRequests(since) {
|
|
3638
|
-
return cloudListItems(this.cloud, "requests", q({ since }));
|
|
3639
|
-
}
|
|
3640
|
-
async estimate(input) {
|
|
3641
|
-
const rows = await this.listPricing();
|
|
3642
|
-
return estimateCostFromRows(rows, input.model, input.inputTokens, input.outputTokens, input.cacheReadTokens ?? 0, input.cacheWriteTokens ?? 0, input.cacheWrite1hTokens ?? 0, input.cacheStorageTokenHours ?? 0);
|
|
3643
|
-
}
|
|
3644
|
-
async setBudget(input) {
|
|
3645
|
-
const created = await this.cloud.client.create("budgets", {
|
|
3646
|
-
project_path: input.project_path,
|
|
3647
|
-
...input.agent ? { agent: input.agent } : {},
|
|
3648
|
-
...input.cost_center_id ? { cost_center_id: input.cost_center_id } : {},
|
|
3649
|
-
period: input.period,
|
|
3650
|
-
limit_usd: input.limit_usd,
|
|
3651
|
-
alert_at_percent: input.alert_at_percent
|
|
3652
|
-
});
|
|
3653
|
-
return created?.id ?? "";
|
|
3654
|
-
}
|
|
3655
|
-
async removeBudget(id) {
|
|
3656
|
-
await this.cloud.client.delete("budgets", id);
|
|
3657
|
-
}
|
|
3658
|
-
async setGoal(input) {
|
|
3659
|
-
await this.cloud.client.create("goals", {
|
|
3660
|
-
period: input.period,
|
|
3661
|
-
project_path: input.project_path,
|
|
3662
|
-
...input.agent ? { agent: input.agent } : {},
|
|
3663
|
-
limit_usd: input.limit_usd
|
|
3664
|
-
});
|
|
3665
|
-
}
|
|
3666
|
-
async removeGoal(id) {
|
|
3667
|
-
await this.cloud.client.delete("goals", id);
|
|
3668
|
-
}
|
|
3669
|
-
async setPricing(input) {
|
|
3670
|
-
await this.cloud.client.create("pricing", { ...input, updated_at: new Date().toISOString() });
|
|
3671
|
-
}
|
|
3672
|
-
async removePricing(model) {
|
|
3673
|
-
await this.cloud.client.delete("pricing", model);
|
|
3674
|
-
}
|
|
3675
|
-
async setSubscription(input) {
|
|
3676
|
-
const now = new Date().toISOString();
|
|
3677
|
-
const row = {
|
|
3678
|
-
id: input.id ?? randomUUID(),
|
|
3679
|
-
provider: input.provider,
|
|
3680
|
-
plan: input.plan,
|
|
3681
|
-
agent: input.agent,
|
|
3682
|
-
monthly_fee_usd: input.monthly_fee_usd,
|
|
3683
|
-
included_usage_usd: input.included_usage_usd,
|
|
3684
|
-
billing_cycle_start: input.billing_cycle_start,
|
|
3685
|
-
reset_policy: input.reset_policy,
|
|
3686
|
-
active: input.active,
|
|
3687
|
-
created_at: now,
|
|
3688
|
-
updated_at: now
|
|
3689
|
-
};
|
|
3690
|
-
await this.cloud.client.create("subscriptions", row);
|
|
3691
|
-
return row;
|
|
3692
|
-
}
|
|
3693
|
-
async removeSubscription(id) {
|
|
3694
|
-
await this.cloud.client.delete("subscriptions", id);
|
|
3695
|
-
}
|
|
3696
|
-
async addProject(path, name) {
|
|
3697
|
-
await this.cloud.client.create("project-registry", { path, name, description: null, tags: [] });
|
|
3698
|
-
}
|
|
3699
|
-
async renameProject(path, name) {
|
|
3700
|
-
await this.cloud.client.delete("project-registry", path);
|
|
3701
|
-
await this.cloud.client.create("project-registry", { path, name, description: null, tags: [] });
|
|
3702
|
-
}
|
|
3703
|
-
async removeProject(path) {
|
|
3704
|
-
await this.cloud.client.delete("project-registry", path);
|
|
3705
|
-
}
|
|
3706
|
-
async sendFeedback(input) {
|
|
3707
|
-
await this.cloud.client.create("feedback", {
|
|
3708
|
-
message: input.message,
|
|
3709
|
-
...input.email ? { email: input.email } : {},
|
|
3710
|
-
category: input.category ?? "general"
|
|
3711
|
-
});
|
|
3712
|
-
}
|
|
3713
|
-
}
|
|
3714
|
-
function getStore(env = process.env) {
|
|
3715
|
-
const cloud = economyCloudStorage(env);
|
|
3716
|
-
return cloud.active ? new ApiStore(cloud) : new LocalStore;
|
|
3717
|
-
}
|
|
3718
|
-
function isCloudStore(env = process.env) {
|
|
3719
|
-
return economyCloudStorage(env).active;
|
|
3720
|
-
}
|
|
3721
|
-
export {
|
|
3722
|
-
isCloudStore,
|
|
3723
|
-
isAgent,
|
|
3724
|
-
getStore,
|
|
3725
|
-
LocalStore,
|
|
3726
|
-
COST_BASIS,
|
|
3727
|
-
ApiStore,
|
|
3728
|
-
AGENTS
|
|
3729
|
-
};
|