@hasna/economy 0.3.9 → 0.3.10

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