@hasna/economy 0.2.19 → 0.2.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/menubar.d.ts.map +1 -1
- package/dist/cli/commands/watch.d.ts +0 -1
- package/dist/cli/commands/watch.d.ts.map +1 -1
- package/dist/cli/index.js +707 -5200
- package/dist/db/database.d.ts +1 -41
- package/dist/db/database.d.ts.map +1 -1
- package/dist/db/pg-migrations.d.ts.map +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +135 -1202
- package/dist/ingest/claude.d.ts +2 -13
- package/dist/ingest/claude.d.ts.map +1 -1
- package/dist/ingest/codex.d.ts +1 -2
- package/dist/ingest/codex.d.ts.map +1 -1
- package/dist/ingest/gemini.d.ts +1 -2
- package/dist/ingest/gemini.d.ts.map +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/gatherer.d.ts.map +1 -1
- package/dist/lib/model-config.d.ts.map +1 -1
- package/dist/lib/pricing.d.ts +3 -3
- package/dist/lib/pricing.d.ts.map +1 -1
- package/dist/lib/webhooks.d.ts +1 -1
- package/dist/lib/webhooks.d.ts.map +1 -1
- package/dist/mcp/index.js +487 -2749
- package/dist/server/index.d.ts +0 -1
- package/dist/server/index.js +196 -3090
- package/dist/server/serve.d.ts +2 -10
- package/dist/server/serve.d.ts.map +1 -1
- package/dist/types/index.d.ts +6 -59
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/cli/commands/completion.d.ts +0 -2
- package/dist/cli/commands/completion.d.ts.map +0 -1
- package/dist/cli/commands/extras.d.ts +0 -4
- package/dist/cli/commands/extras.d.ts.map +0 -1
- package/dist/cli/commands/notification.d.ts +0 -8
- package/dist/cli/commands/notification.d.ts.map +0 -1
- package/dist/cli/commands/todos.d.ts +0 -26
- package/dist/cli/commands/todos.d.ts.map +0 -1
- package/dist/cli/commands/tui.d.ts +0 -10
- package/dist/cli/commands/tui.d.ts.map +0 -1
- package/dist/ingest/billing.d.ts +0 -27
- package/dist/ingest/billing.d.ts.map +0 -1
- package/dist/ingest/claude-quota.d.ts +0 -5
- package/dist/ingest/claude-quota.d.ts.map +0 -1
- package/dist/ingest/codex-quota.d.ts +0 -5
- package/dist/ingest/codex-quota.d.ts.map +0 -1
- package/dist/ingest/cursor.d.ts +0 -6
- package/dist/ingest/cursor.d.ts.map +0 -1
- package/dist/ingest/hermes.d.ts +0 -6
- package/dist/ingest/hermes.d.ts.map +0 -1
- package/dist/ingest/opencode.d.ts +0 -7
- package/dist/ingest/opencode.d.ts.map +0 -1
- package/dist/ingest/otel.d.ts +0 -20
- package/dist/ingest/otel.d.ts.map +0 -1
- package/dist/ingest/pi.d.ts +0 -7
- package/dist/ingest/pi.d.ts.map +0 -1
- package/dist/ingest/plugin.d.ts +0 -17
- package/dist/ingest/plugin.d.ts.map +0 -1
- package/dist/lib/agents.d.ts +0 -11
- package/dist/lib/agents.d.ts.map +0 -1
- package/dist/lib/billing-diff.d.ts +0 -22
- package/dist/lib/billing-diff.d.ts.map +0 -1
- package/dist/lib/cloud-sync.d.ts +0 -35
- package/dist/lib/cloud-sync.d.ts.map +0 -1
- package/dist/lib/open-projects.d.ts +0 -19
- package/dist/lib/open-projects.d.ts.map +0 -1
- package/dist/lib/package-metadata.d.ts +0 -8
- package/dist/lib/package-metadata.d.ts.map +0 -1
- package/dist/lib/paths.d.ts +0 -20
- package/dist/lib/paths.d.ts.map +0 -1
- package/dist/lib/savings.d.ts +0 -17
- package/dist/lib/savings.d.ts.map +0 -1
- package/dist/lib/serve-auth.d.ts +0 -4
- package/dist/lib/serve-auth.d.ts.map +0 -1
- package/dist/lib/spikes.d.ts +0 -18
- package/dist/lib/spikes.d.ts.map +0 -1
- package/dist/lib/sync-all.d.ts +0 -28
- package/dist/lib/sync-all.d.ts.map +0 -1
- package/dist/lib/watch-paths.d.ts +0 -3
- package/dist/lib/watch-paths.d.ts.map +0 -1
- package/dist/mcp/http.d.ts +0 -12
- package/dist/mcp/http.d.ts.map +0 -1
- package/dist/mcp/server.d.ts +0 -4
- package/dist/mcp/server.d.ts.map +0 -1
- package/dist/otel/index.d.ts +0 -3
- package/dist/otel/index.d.ts.map +0 -1
- package/dist/otel/index.js +0 -1372
package/dist/index.js
CHANGED
|
@@ -14,7 +14,6 @@ var __export = (target, all) => {
|
|
|
14
14
|
});
|
|
15
15
|
};
|
|
16
16
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
|
-
var __require = import.meta.require;
|
|
18
17
|
|
|
19
18
|
// src/lib/pricing.ts
|
|
20
19
|
var exports_pricing = {};
|
|
@@ -28,503 +27,88 @@ __export(exports_pricing, {
|
|
|
28
27
|
DEFAULT_PRICING: () => DEFAULT_PRICING
|
|
29
28
|
});
|
|
30
29
|
function normalizeModelName(raw) {
|
|
31
|
-
return raw.
|
|
32
|
-
}
|
|
33
|
-
function normalizeModelNamePreservingProvider(raw) {
|
|
34
|
-
return raw.trim().toLowerCase().replace(/^models\//, "").replace(/:.+$/, "").replace(/-\d{8}$/, "").replace(/-\d{4}-\d{2}-\d{2}$/, "");
|
|
35
|
-
}
|
|
36
|
-
function modelLookupKeys(raw) {
|
|
37
|
-
const withProvider = normalizeModelNamePreservingProvider(raw);
|
|
38
|
-
const withoutProvider = normalizeModelName(raw);
|
|
39
|
-
return withProvider === withoutProvider ? [withoutProvider] : [withProvider, withoutProvider];
|
|
40
|
-
}
|
|
41
|
-
function bestPrefixMatch(normalized, entries) {
|
|
42
|
-
let best = null;
|
|
43
|
-
for (const entry of entries) {
|
|
44
|
-
const [key] = entry;
|
|
45
|
-
if (normalized !== key && !normalized.startsWith(`${key}-`))
|
|
46
|
-
continue;
|
|
47
|
-
if (!best || key.length > best[0].length)
|
|
48
|
-
best = entry;
|
|
49
|
-
}
|
|
50
|
-
return best?.[1] ?? null;
|
|
51
|
-
}
|
|
52
|
-
function bestModelMatch(model, entries) {
|
|
53
|
-
for (const key of modelLookupKeys(model)) {
|
|
54
|
-
const match = bestPrefixMatch(key, entries);
|
|
55
|
-
if (match)
|
|
56
|
-
return match;
|
|
57
|
-
}
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
function exactModelMatch(model, entries) {
|
|
61
|
-
for (const key of modelLookupKeys(model)) {
|
|
62
|
-
const match = entries.find(([entryKey]) => entryKey === key);
|
|
63
|
-
if (match)
|
|
64
|
-
return match[1];
|
|
65
|
-
}
|
|
66
|
-
return null;
|
|
30
|
+
return raw.replace(/-\d{8}$/, "").replace(/-\d{4}-\d{2}-\d{2}$/, "").toLowerCase();
|
|
67
31
|
}
|
|
68
32
|
function ensurePricingSeeded(db) {
|
|
69
33
|
seedModelPricing(db, DEFAULT_PRICING);
|
|
70
|
-
repairLegacySeededPricing(db);
|
|
71
|
-
repairMissingDefaultCacheWrite1h(db);
|
|
72
|
-
repairMissingDefaultCacheStorage(db);
|
|
73
|
-
removeDeprecatedDefaultPricing(db);
|
|
74
|
-
}
|
|
75
|
-
function repairLegacySeededPricing(db) {
|
|
76
|
-
const now = new Date().toISOString();
|
|
77
|
-
const legacyModels = new Set([
|
|
78
|
-
...Object.keys(LEGACY_DEFAULT_PRICING),
|
|
79
|
-
...Object.keys(ADDITIONAL_LEGACY_DEFAULT_PRICING)
|
|
80
|
-
]);
|
|
81
|
-
for (const model of legacyModels) {
|
|
82
|
-
const current = getModelPricing(db, model);
|
|
83
|
-
const next = DEFAULT_PRICING[model];
|
|
84
|
-
if (!current || !next)
|
|
85
|
-
continue;
|
|
86
|
-
const legacy = LEGACY_DEFAULT_PRICING[model];
|
|
87
|
-
const legacyRows = [
|
|
88
|
-
...legacy ? [legacy] : [],
|
|
89
|
-
...ADDITIONAL_LEGACY_DEFAULT_PRICING[model] ?? []
|
|
90
|
-
];
|
|
91
|
-
if (!legacyRows.some((row) => samePricing(current, row)))
|
|
92
|
-
continue;
|
|
93
|
-
upsertModelPricing(db, {
|
|
94
|
-
model,
|
|
95
|
-
input_per_1m: next.inputPer1M,
|
|
96
|
-
output_per_1m: next.outputPer1M,
|
|
97
|
-
cache_read_per_1m: next.cacheReadPer1M,
|
|
98
|
-
cache_write_per_1m: next.cacheWritePer1M,
|
|
99
|
-
cache_write_1h_per_1m: next.cacheWrite1hPer1M ?? 0,
|
|
100
|
-
cache_storage_per_1m_hour: next.cacheStoragePer1MHour ?? 0,
|
|
101
|
-
updated_at: now
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
function repairMissingDefaultCacheWrite1h(db) {
|
|
106
|
-
const now = new Date().toISOString();
|
|
107
|
-
for (const [model, next] of Object.entries(DEFAULT_PRICING)) {
|
|
108
|
-
if (!next.cacheWrite1hPer1M)
|
|
109
|
-
continue;
|
|
110
|
-
const current = getModelPricing(db, model);
|
|
111
|
-
if (!current)
|
|
112
|
-
continue;
|
|
113
|
-
if ((current.cache_write_1h_per_1m ?? 0) !== 0)
|
|
114
|
-
continue;
|
|
115
|
-
if (!sameBasePricing(current, next))
|
|
116
|
-
continue;
|
|
117
|
-
upsertModelPricing(db, {
|
|
118
|
-
model,
|
|
119
|
-
input_per_1m: current.input_per_1m,
|
|
120
|
-
output_per_1m: current.output_per_1m,
|
|
121
|
-
cache_read_per_1m: current.cache_read_per_1m,
|
|
122
|
-
cache_write_per_1m: current.cache_write_per_1m,
|
|
123
|
-
cache_write_1h_per_1m: next.cacheWrite1hPer1M,
|
|
124
|
-
cache_storage_per_1m_hour: current.cache_storage_per_1m_hour ?? next.cacheStoragePer1MHour ?? 0,
|
|
125
|
-
updated_at: now
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
function repairMissingDefaultCacheStorage(db) {
|
|
130
|
-
const now = new Date().toISOString();
|
|
131
|
-
for (const [model, next] of Object.entries(DEFAULT_PRICING)) {
|
|
132
|
-
if (!next.cacheStoragePer1MHour)
|
|
133
|
-
continue;
|
|
134
|
-
const current = getModelPricing(db, model);
|
|
135
|
-
if (!current)
|
|
136
|
-
continue;
|
|
137
|
-
if ((current.cache_storage_per_1m_hour ?? 0) !== 0)
|
|
138
|
-
continue;
|
|
139
|
-
if (!sameBasePricing(current, next))
|
|
140
|
-
continue;
|
|
141
|
-
upsertModelPricing(db, {
|
|
142
|
-
model,
|
|
143
|
-
input_per_1m: current.input_per_1m,
|
|
144
|
-
output_per_1m: current.output_per_1m,
|
|
145
|
-
cache_read_per_1m: current.cache_read_per_1m,
|
|
146
|
-
cache_write_per_1m: current.cache_write_per_1m,
|
|
147
|
-
cache_write_1h_per_1m: current.cache_write_1h_per_1m ?? next.cacheWrite1hPer1M ?? 0,
|
|
148
|
-
cache_storage_per_1m_hour: next.cacheStoragePer1MHour,
|
|
149
|
-
updated_at: now
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
function removeDeprecatedDefaultPricing(db) {
|
|
154
|
-
for (const [model, removedRows] of Object.entries(REMOVED_DEFAULT_PRICING)) {
|
|
155
|
-
const current = getModelPricing(db, model);
|
|
156
|
-
if (!current)
|
|
157
|
-
continue;
|
|
158
|
-
if (!removedRows.some((row) => samePricing(current, row)))
|
|
159
|
-
continue;
|
|
160
|
-
deleteModelPricing(db, model);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
function sameBasePricing(row, pricing) {
|
|
164
|
-
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;
|
|
165
|
-
}
|
|
166
|
-
function samePricing(row, pricing) {
|
|
167
|
-
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);
|
|
168
34
|
}
|
|
169
35
|
function getPricingFromDb(db, model) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
36
|
+
const normalized = normalizeModelName(model);
|
|
37
|
+
const row = getModelPricing(db, normalized);
|
|
38
|
+
if (row) {
|
|
39
|
+
return {
|
|
40
|
+
inputPer1M: row.input_per_1m,
|
|
41
|
+
outputPer1M: row.output_per_1m,
|
|
42
|
+
cacheReadPer1M: row.cache_read_per_1m,
|
|
43
|
+
cacheWritePer1M: row.cache_write_per_1m
|
|
44
|
+
};
|
|
176
45
|
}
|
|
177
46
|
const allRows = db.prepare(`SELECT * FROM model_pricing`).all();
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const seeded = DEFAULT_PRICING[row.model];
|
|
185
|
-
const cacheWrite1hPer1M = seeded?.cacheWrite1hPer1M && (row.cache_write_1h_per_1m ?? 0) === 0 && sameBasePricing(row, seeded) ? seeded.cacheWrite1hPer1M : row.cache_write_1h_per_1m ?? 0;
|
|
186
|
-
return {
|
|
187
|
-
inputPer1M: row.input_per_1m,
|
|
188
|
-
outputPer1M: row.output_per_1m,
|
|
189
|
-
cacheReadPer1M: row.cache_read_per_1m,
|
|
190
|
-
cacheWritePer1M: row.cache_write_per_1m,
|
|
191
|
-
cacheWrite1hPer1M,
|
|
192
|
-
cacheStoragePer1MHour: row.cache_storage_per_1m_hour ?? seeded?.cacheStoragePer1MHour ?? 0
|
|
193
|
-
};
|
|
47
|
+
for (const r of allRows) {
|
|
48
|
+
if (normalized.startsWith(r.model)) {
|
|
49
|
+
return { inputPer1M: r.input_per_1m, outputPer1M: r.output_per_1m, cacheReadPer1M: r.cache_read_per_1m, cacheWritePer1M: r.cache_write_per_1m };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
194
53
|
}
|
|
195
54
|
function getPricing(model) {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
55
|
+
const normalized = normalizeModelName(model);
|
|
56
|
+
if (DEFAULT_PRICING[normalized])
|
|
57
|
+
return DEFAULT_PRICING[normalized] ?? null;
|
|
58
|
+
for (const key of Object.keys(DEFAULT_PRICING)) {
|
|
59
|
+
if (normalized.startsWith(key))
|
|
60
|
+
return DEFAULT_PRICING[key] ?? null;
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
202
63
|
}
|
|
203
|
-
function computeCost(model, inputTokens, outputTokens, cacheReadTokens = 0, cacheWriteTokens = 0
|
|
64
|
+
function computeCost(model, inputTokens, outputTokens, cacheReadTokens = 0, cacheWriteTokens = 0) {
|
|
204
65
|
const pricing = getPricing(model);
|
|
205
66
|
if (!pricing)
|
|
206
67
|
return 0;
|
|
207
|
-
return
|
|
68
|
+
return (inputTokens * pricing.inputPer1M + outputTokens * pricing.outputPer1M + cacheReadTokens * pricing.cacheReadPer1M + cacheWriteTokens * pricing.cacheWritePer1M) / 1e6;
|
|
208
69
|
}
|
|
209
|
-
function computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens = 0, cacheWriteTokens = 0
|
|
70
|
+
function computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens = 0, cacheWriteTokens = 0) {
|
|
210
71
|
const pricing = getPricingFromDb(db, model) ?? getPricing(model);
|
|
211
72
|
if (!pricing)
|
|
212
73
|
return 0;
|
|
213
|
-
return
|
|
74
|
+
return (inputTokens * pricing.inputPer1M + outputTokens * pricing.outputPer1M + cacheReadTokens * pricing.cacheReadPer1M + cacheWriteTokens * pricing.cacheWritePer1M) / 1e6;
|
|
214
75
|
}
|
|
215
|
-
|
|
216
|
-
if (isFreeModel(model))
|
|
217
|
-
return 0;
|
|
218
|
-
let effective = pricing;
|
|
219
|
-
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));
|
|
220
|
-
if (promptTier) {
|
|
221
|
-
const billablePromptTokens = inputTokens + cacheReadTokens + cacheWriteTokens + cacheWrite1hTokens;
|
|
222
|
-
if (billablePromptTokens > promptTier.threshold) {
|
|
223
|
-
effective = {
|
|
224
|
-
...pricing,
|
|
225
|
-
inputPer1M: promptTier.inputPer1M ?? pricing.inputPer1M * (promptTier.inputMultiplier ?? 1),
|
|
226
|
-
outputPer1M: promptTier.outputPer1M ?? pricing.outputPer1M * (promptTier.outputMultiplier ?? 1),
|
|
227
|
-
cacheReadPer1M: promptTier.cacheReadPer1M ?? pricing.cacheReadPer1M * (promptTier.cacheReadMultiplier ?? 1),
|
|
228
|
-
cacheWritePer1M: promptTier.cacheWritePer1M ?? pricing.cacheWritePer1M * (promptTier.cacheWriteMultiplier ?? 1)
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
return (inputTokens * effective.inputPer1M + outputTokens * effective.outputPer1M + cacheReadTokens * effective.cacheReadPer1M + cacheWriteTokens * effective.cacheWritePer1M + cacheWrite1hTokens * (effective.cacheWrite1hPer1M ?? effective.cacheWritePer1M) + cacheStorageTokenHours * (effective.cacheStoragePer1MHour ?? 0)) / 1e6;
|
|
233
|
-
}
|
|
234
|
-
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;
|
|
76
|
+
var DEFAULT_PRICING;
|
|
235
77
|
var init_pricing = __esm(() => {
|
|
236
78
|
init_database();
|
|
237
79
|
DEFAULT_PRICING = {
|
|
238
|
-
"claude-opus-4-
|
|
239
|
-
"claude-opus-4-
|
|
240
|
-
"claude-
|
|
241
|
-
"claude-
|
|
242
|
-
"claude-
|
|
243
|
-
"claude-
|
|
244
|
-
"claude-sonnet-4-5": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 6 },
|
|
245
|
-
"claude-sonnet-4": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 6 },
|
|
246
|
-
"claude-3-7-sonnet": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 6 },
|
|
247
|
-
"claude-haiku-4-5": { inputPer1M: 1, outputPer1M: 5, cacheReadPer1M: 0.1, cacheWritePer1M: 1.25, cacheWrite1hPer1M: 2 },
|
|
248
|
-
"claude-3-5-haiku": { inputPer1M: 0.8, outputPer1M: 4, cacheReadPer1M: 0.08, cacheWritePer1M: 1, cacheWrite1hPer1M: 1.6 },
|
|
249
|
-
"claude-3-opus": { inputPer1M: 15, outputPer1M: 75, cacheReadPer1M: 1.5, cacheWritePer1M: 18.75, cacheWrite1hPer1M: 30 },
|
|
250
|
-
"claude-3-haiku": { inputPer1M: 0.25, outputPer1M: 1.25, cacheReadPer1M: 0.03, cacheWritePer1M: 0.3, cacheWrite1hPer1M: 0.5 },
|
|
251
|
-
"gemini-3.1-pro-preview": { inputPer1M: 2, outputPer1M: 12, cacheReadPer1M: 0.2, cacheWritePer1M: 0, cacheStoragePer1MHour: 4.5 },
|
|
252
|
-
"gemini-3.1-flash-lite-preview": { inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0.025, cacheWritePer1M: 0, cacheStoragePer1MHour: 1 },
|
|
253
|
-
"gemini-3.1-flash-lite": { inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0.025, cacheWritePer1M: 0, cacheStoragePer1MHour: 1 },
|
|
254
|
-
"gemini-3-flash-preview": { inputPer1M: 0.5, outputPer1M: 3, cacheReadPer1M: 0.05, cacheWritePer1M: 0, cacheStoragePer1MHour: 1 },
|
|
255
|
-
"gemini-2.5-pro": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0.125, cacheWritePer1M: 0, cacheStoragePer1MHour: 4.5 },
|
|
256
|
-
"gemini-2.5-flash": { inputPer1M: 0.3, outputPer1M: 2.5, cacheReadPer1M: 0.03, cacheWritePer1M: 0, cacheStoragePer1MHour: 1 },
|
|
257
|
-
"gemini-2.5-flash-lite": { inputPer1M: 0.1, outputPer1M: 0.4, cacheReadPer1M: 0.01, cacheWritePer1M: 0, cacheStoragePer1MHour: 1 },
|
|
258
|
-
"gemini-2.0-flash": { inputPer1M: 0.1, outputPer1M: 0.4, cacheReadPer1M: 0.025, cacheWritePer1M: 0, cacheStoragePer1MHour: 1 },
|
|
259
|
-
"gemini-2.0-flash-lite": { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
260
|
-
"google/gemini-3.1-pro-preview": { inputPer1M: 2, outputPer1M: 12, cacheReadPer1M: 0.2, cacheWritePer1M: 0.375 },
|
|
261
|
-
"google/gemini-3.1-flash-lite-preview": { inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0.025, cacheWritePer1M: 0.08333333333333334 },
|
|
262
|
-
"google/gemini-3.1-flash-lite": { inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0.025, cacheWritePer1M: 0.08333333333333334 },
|
|
263
|
-
"google/gemini-3-flash-preview": { inputPer1M: 0.5, outputPer1M: 3, cacheReadPer1M: 0.05, cacheWritePer1M: 0.08333333333333334 },
|
|
264
|
-
"google/gemini-2.5-pro": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0.125, cacheWritePer1M: 0.375 },
|
|
265
|
-
"google/gemini-2.5-flash": { inputPer1M: 0.3, outputPer1M: 2.5, cacheReadPer1M: 0.03, cacheWritePer1M: 0.08333333333333334 },
|
|
266
|
-
"google/gemini-2.5-flash-lite": { inputPer1M: 0.1, outputPer1M: 0.4, cacheReadPer1M: 0.01, cacheWritePer1M: 0.08333333333333334 },
|
|
267
|
-
"gpt-5.5": { inputPer1M: 5, outputPer1M: 30, cacheReadPer1M: 0.5, cacheWritePer1M: 0 },
|
|
268
|
-
"gpt-5.5-pro": { inputPer1M: 30, outputPer1M: 180, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
269
|
-
"gpt-5.4": { inputPer1M: 2.5, outputPer1M: 15, cacheReadPer1M: 0.25, cacheWritePer1M: 0 },
|
|
270
|
-
"gpt-5.4-pro": { inputPer1M: 30, outputPer1M: 180, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
271
|
-
"gpt-5.4-mini": { inputPer1M: 0.75, outputPer1M: 4.5, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
|
|
272
|
-
"gpt-5.4-nano": { inputPer1M: 0.2, outputPer1M: 1.25, cacheReadPer1M: 0.02, cacheWritePer1M: 0 },
|
|
273
|
-
"gpt-5.3-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.175, cacheWritePer1M: 0 },
|
|
274
|
-
"gpt-5.2-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.175, cacheWritePer1M: 0 },
|
|
275
|
-
"gpt-5.2-chat-latest": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.175, cacheWritePer1M: 0 },
|
|
276
|
-
"gpt-5.2": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.175, cacheWritePer1M: 0 },
|
|
277
|
-
"gpt-5-codex": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0.125, cacheWritePer1M: 0 },
|
|
278
|
-
"gpt-5-mini": { inputPer1M: 0.25, outputPer1M: 2, cacheReadPer1M: 0.025, cacheWritePer1M: 0 },
|
|
279
|
-
"gpt-5": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0.125, cacheWritePer1M: 0 },
|
|
280
|
-
"gpt-4o": { inputPer1M: 2.5, outputPer1M: 10, cacheReadPer1M: 1.25, cacheWritePer1M: 0 },
|
|
281
|
-
"gpt-4o-mini": { inputPer1M: 0.15, outputPer1M: 0.6, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
|
|
282
|
-
o1: { inputPer1M: 15, outputPer1M: 60, cacheReadPer1M: 7.5, cacheWritePer1M: 0 },
|
|
283
|
-
"o1-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.55, cacheWritePer1M: 0 },
|
|
284
|
-
o3: { inputPer1M: 2, outputPer1M: 8, cacheReadPer1M: 0.5, cacheWritePer1M: 0 },
|
|
285
|
-
"o3-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.55, cacheWritePer1M: 0 },
|
|
286
|
-
"o4-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.275, cacheWritePer1M: 0 },
|
|
287
|
-
"qwen3.6-plus": { inputPer1M: 0.325, outputPer1M: 1.95, cacheReadPer1M: 0.0325, cacheWritePer1M: 0.40625 },
|
|
288
|
-
"qwen3.6-flash": { inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0.025, cacheWritePer1M: 0.3125 },
|
|
289
|
-
"qwen3.6-35b-a3b": { inputPer1M: 0.15, outputPer1M: 1, cacheReadPer1M: 0.05, cacheWritePer1M: 0 },
|
|
290
|
-
"qwen3.6-max-preview": { inputPer1M: 1.04, outputPer1M: 6.24, cacheReadPer1M: 0.104, cacheWritePer1M: 1.3 },
|
|
291
|
-
"qwen3.6-27b": { inputPer1M: 0.32, outputPer1M: 3.2, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
292
|
-
"qwen/qwen3.6-plus": { inputPer1M: 0.325, outputPer1M: 1.95, cacheReadPer1M: 0.0325, cacheWritePer1M: 0.40625 },
|
|
293
|
-
"qwen/qwen3.6-flash": { inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0.025, cacheWritePer1M: 0.3125 },
|
|
294
|
-
"qwen/qwen3.6-35b-a3b": { inputPer1M: 0.15, outputPer1M: 1, cacheReadPer1M: 0.05, cacheWritePer1M: 0 },
|
|
295
|
-
"qwen/qwen3.6-max-preview": { inputPer1M: 1.04, outputPer1M: 6.24, cacheReadPer1M: 0.104, cacheWritePer1M: 1.3 },
|
|
296
|
-
"qwen/qwen3.6-27b": { inputPer1M: 0.32, outputPer1M: 3.2, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
297
|
-
"minimax-m2.7": { inputPer1M: 0.3, outputPer1M: 1.2, cacheReadPer1M: 0.06, cacheWritePer1M: 0.375 },
|
|
298
|
-
"minimax-m2.7-highspeed": { inputPer1M: 0.6, outputPer1M: 2.4, cacheReadPer1M: 0.06, cacheWritePer1M: 0.375 },
|
|
299
|
-
"minimax/minimax-m2.7": { inputPer1M: 0.299, outputPer1M: 1.2, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
300
|
-
"minimax-m1": { inputPer1M: 0.4, outputPer1M: 2.2, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
301
|
-
"minimax/minimax-m1": { inputPer1M: 0.4, outputPer1M: 2.2, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
302
|
-
"grok-4.3": { inputPer1M: 1.25, outputPer1M: 2.5, cacheReadPer1M: 0.2, cacheWritePer1M: 0 },
|
|
303
|
-
"grok-latest": { inputPer1M: 1.25, outputPer1M: 2.5, cacheReadPer1M: 0.2, cacheWritePer1M: 0 },
|
|
304
|
-
"grok-4.20": { inputPer1M: 1.25, outputPer1M: 2.5, cacheReadPer1M: 0.2, cacheWritePer1M: 0 },
|
|
305
|
-
"grok-4-1-fast": { inputPer1M: 0.2, outputPer1M: 0.5, cacheReadPer1M: 0.05, cacheWritePer1M: 0 },
|
|
306
|
-
"grok-4-fast": { inputPer1M: 0.2, outputPer1M: 0.5, cacheReadPer1M: 0.05, cacheWritePer1M: 0 },
|
|
307
|
-
"grok-4": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.75, cacheWritePer1M: 0 },
|
|
308
|
-
"grok-code-fast-1": { inputPer1M: 0.2, outputPer1M: 1.5, cacheReadPer1M: 0.02, cacheWritePer1M: 0 },
|
|
309
|
-
"grok-code-fast": { inputPer1M: 0.2, outputPer1M: 1.5, cacheReadPer1M: 0.02, cacheWritePer1M: 0 },
|
|
310
|
-
"grok-3": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.75, cacheWritePer1M: 0 },
|
|
311
|
-
"grok-3-mini": { inputPer1M: 0.3, outputPer1M: 0.5, cacheReadPer1M: 0.07, cacheWritePer1M: 0 },
|
|
312
|
-
"glm-5.1": { inputPer1M: 1.4, outputPer1M: 4.4, cacheReadPer1M: 0.26, cacheWritePer1M: 0 },
|
|
313
|
-
"glm-5": { inputPer1M: 1, outputPer1M: 3.2, cacheReadPer1M: 0.2, cacheWritePer1M: 0 },
|
|
314
|
-
"z-ai/glm-5.1": { inputPer1M: 1.05, outputPer1M: 3.5, cacheReadPer1M: 0.525, cacheWritePer1M: 0 },
|
|
315
|
-
"z-ai/glm-5": { inputPer1M: 0.6, outputPer1M: 1.92, cacheReadPer1M: 0.12, cacheWritePer1M: 0 },
|
|
316
|
-
"kimi-k2.6": { inputPer1M: 0.95, outputPer1M: 4, cacheReadPer1M: 0.16, cacheWritePer1M: 0 },
|
|
317
|
-
"kimi-k2.5": { inputPer1M: 0.6, outputPer1M: 3, cacheReadPer1M: 0.1, cacheWritePer1M: 0 },
|
|
318
|
-
"kimi-k2": { inputPer1M: 0.6, outputPer1M: 2.5, cacheReadPer1M: 0.15, cacheWritePer1M: 0 },
|
|
319
|
-
"moonshotai/kimi-k2.6": { inputPer1M: 0.75, outputPer1M: 3.5, cacheReadPer1M: 0.15, cacheWritePer1M: 0 },
|
|
320
|
-
"moonshotai/kimi-k2.5": { inputPer1M: 0.44, outputPer1M: 2, cacheReadPer1M: 0.22, cacheWritePer1M: 0 },
|
|
321
|
-
"moonshotai/kimi-k2": { inputPer1M: 0.57, outputPer1M: 2.3, cacheReadPer1M: 0, cacheWritePer1M: 0 }
|
|
322
|
-
};
|
|
323
|
-
LEGACY_DEFAULT_PRICING = {
|
|
80
|
+
"claude-opus-4-6": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25 },
|
|
81
|
+
"claude-opus-4-5": { inputPer1M: 5, outputPer1M: 25, cacheReadPer1M: 0.5, cacheWritePer1M: 6.25 },
|
|
82
|
+
"claude-sonnet-4-6": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75 },
|
|
83
|
+
"claude-sonnet-4-5": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75 },
|
|
84
|
+
"claude-haiku-4-5": { inputPer1M: 1, outputPer1M: 5, cacheReadPer1M: 0.1, cacheWritePer1M: 1.25 },
|
|
85
|
+
"claude-3-5-sonnet": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75 },
|
|
324
86
|
"claude-3-5-haiku": { inputPer1M: 1, outputPer1M: 5, cacheReadPer1M: 0.1, cacheWritePer1M: 1.25 },
|
|
325
|
-
"claude-opus
|
|
326
|
-
"
|
|
327
|
-
"
|
|
87
|
+
"claude-3-opus": { inputPer1M: 15, outputPer1M: 75, cacheReadPer1M: 1.5, cacheWritePer1M: 18.75 },
|
|
88
|
+
"claude-3-sonnet": { inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75 },
|
|
89
|
+
"claude-3-haiku": { inputPer1M: 0.25, outputPer1M: 1.25, cacheReadPer1M: 0.03, cacheWritePer1M: 0.3 },
|
|
328
90
|
"gemini-2.0-flash": { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
91
|
+
"gemini-2.5-pro": { inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
92
|
+
"gemini-1.5-pro": { inputPer1M: 1.25, outputPer1M: 5, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
93
|
+
"gemini-1.5-flash": { inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
329
94
|
"gpt-5.3-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
330
95
|
"gpt-5.2-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
331
96
|
"gpt-5-codex": { inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.44, cacheWritePer1M: 0 },
|
|
332
|
-
"gpt-
|
|
333
|
-
"gpt-
|
|
97
|
+
"gpt-4o": { inputPer1M: 2.5, outputPer1M: 10, cacheReadPer1M: 1.25, cacheWritePer1M: 0 },
|
|
98
|
+
"gpt-4o-mini": { inputPer1M: 0.15, outputPer1M: 0.6, cacheReadPer1M: 0.075, cacheWritePer1M: 0 },
|
|
99
|
+
o1: { inputPer1M: 15, outputPer1M: 60, cacheReadPer1M: 7.5, cacheWritePer1M: 0 },
|
|
334
100
|
"o1-mini": { inputPer1M: 3, outputPer1M: 12, cacheReadPer1M: 1.5, cacheWritePer1M: 0 },
|
|
335
|
-
|
|
336
|
-
"
|
|
337
|
-
"
|
|
338
|
-
"minimax-m2.7": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
339
|
-
"minimax-m2.7-highspeed": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
340
|
-
"minimax-m1": { inputPer1M: 0.2, outputPer1M: 1.1, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
341
|
-
"glm-5.1": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
342
|
-
"glm-5": { inputPer1M: 0.7, outputPer1M: 0.7, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
343
|
-
"kimi-k2": { inputPer1M: 0.6, outputPer1M: 0.6, cacheReadPer1M: 0, cacheWritePer1M: 0 },
|
|
344
|
-
o3: { inputPer1M: 10, outputPer1M: 40, cacheReadPer1M: 2.5, cacheWritePer1M: 0 }
|
|
345
|
-
};
|
|
346
|
-
ADDITIONAL_LEGACY_DEFAULT_PRICING = {
|
|
347
|
-
"gemini-2.5-pro": [
|
|
348
|
-
{ inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0, cacheWritePer1M: 0 }
|
|
349
|
-
],
|
|
350
|
-
"qwen3.6-plus": [
|
|
351
|
-
{ inputPer1M: 0.325, outputPer1M: 1.95, cacheReadPer1M: 0, cacheWritePer1M: 0.40625 },
|
|
352
|
-
{ inputPer1M: 0.325, outputPer1M: 1.95, cacheReadPer1M: 0.05, cacheWritePer1M: 0.40625 }
|
|
353
|
-
],
|
|
354
|
-
"qwen3.6-flash": [
|
|
355
|
-
{ inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0, cacheWritePer1M: 0.3125 }
|
|
356
|
-
],
|
|
357
|
-
"qwen3.6-max-preview": [
|
|
358
|
-
{ inputPer1M: 1.04, outputPer1M: 6.24, cacheReadPer1M: 0, cacheWritePer1M: 1.3 },
|
|
359
|
-
{ inputPer1M: 1.04, outputPer1M: 6.24, cacheReadPer1M: 0.13, cacheWritePer1M: 1.3 }
|
|
360
|
-
],
|
|
361
|
-
"qwen/qwen3.6-plus": [
|
|
362
|
-
{ inputPer1M: 0.325, outputPer1M: 1.95, cacheReadPer1M: 0, cacheWritePer1M: 0.40625 },
|
|
363
|
-
{ inputPer1M: 0.325, outputPer1M: 1.95, cacheReadPer1M: 0.05, cacheWritePer1M: 0.40625 }
|
|
364
|
-
],
|
|
365
|
-
"qwen/qwen3.6-flash": [
|
|
366
|
-
{ inputPer1M: 0.25, outputPer1M: 1.5, cacheReadPer1M: 0, cacheWritePer1M: 0.3125 }
|
|
367
|
-
],
|
|
368
|
-
"qwen/qwen3.6-max-preview": [
|
|
369
|
-
{ inputPer1M: 1.04, outputPer1M: 6.24, cacheReadPer1M: 0, cacheWritePer1M: 1.3 },
|
|
370
|
-
{ inputPer1M: 1.04, outputPer1M: 6.24, cacheReadPer1M: 0.13, cacheWritePer1M: 1.3 }
|
|
371
|
-
]
|
|
372
|
-
};
|
|
373
|
-
REMOVED_DEFAULT_PRICING = {
|
|
374
|
-
"claude-3-5-sonnet": [
|
|
375
|
-
{ inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 6 },
|
|
376
|
-
{ inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 0 }
|
|
377
|
-
],
|
|
378
|
-
"claude-3-sonnet": [
|
|
379
|
-
{ inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 6 },
|
|
380
|
-
{ inputPer1M: 3, outputPer1M: 15, cacheReadPer1M: 0.3, cacheWritePer1M: 3.75, cacheWrite1hPer1M: 0 }
|
|
381
|
-
],
|
|
382
|
-
"gemini-3.1-pro": [
|
|
383
|
-
{ inputPer1M: 2, outputPer1M: 12, cacheReadPer1M: 0.2, cacheWritePer1M: 0 },
|
|
384
|
-
{ inputPer1M: 1.25, outputPer1M: 10, cacheReadPer1M: 0.31, cacheWritePer1M: 0 }
|
|
385
|
-
],
|
|
386
|
-
"gemini-1.5-pro": [
|
|
387
|
-
{ inputPer1M: 1.25, outputPer1M: 5, cacheReadPer1M: 0, cacheWritePer1M: 0 }
|
|
388
|
-
],
|
|
389
|
-
"gemini-1.5-flash": [
|
|
390
|
-
{ inputPer1M: 0.075, outputPer1M: 0.3, cacheReadPer1M: 0, cacheWritePer1M: 0 }
|
|
391
|
-
],
|
|
392
|
-
"gpt-5.3-chat": [
|
|
393
|
-
{ inputPer1M: 1.75, outputPer1M: 14, cacheReadPer1M: 0.175, cacheWritePer1M: 0 },
|
|
394
|
-
{ inputPer1M: 2, outputPer1M: 8, cacheReadPer1M: 0.5, cacheWritePer1M: 0 }
|
|
395
|
-
],
|
|
396
|
-
"qwen3.6": [
|
|
397
|
-
{ inputPer1M: 0.3, outputPer1M: 0.6, cacheReadPer1M: 0, cacheWritePer1M: 0 }
|
|
398
|
-
]
|
|
399
|
-
};
|
|
400
|
-
FREE_PRICING = {
|
|
401
|
-
inputPer1M: 0,
|
|
402
|
-
outputPer1M: 0,
|
|
403
|
-
cacheReadPer1M: 0,
|
|
404
|
-
cacheWritePer1M: 0,
|
|
405
|
-
cacheWrite1hPer1M: 0,
|
|
406
|
-
cacheStoragePer1MHour: 0
|
|
407
|
-
};
|
|
408
|
-
GEMINI_PROMPT_TIERS = {
|
|
409
|
-
"gemini-3.1-pro-preview": {
|
|
410
|
-
threshold: 200000,
|
|
411
|
-
inputPer1M: 4,
|
|
412
|
-
outputPer1M: 18,
|
|
413
|
-
cacheReadPer1M: 0.4
|
|
414
|
-
},
|
|
415
|
-
"gemini-2.5-pro": {
|
|
416
|
-
threshold: 200000,
|
|
417
|
-
inputPer1M: 2.5,
|
|
418
|
-
outputPer1M: 15,
|
|
419
|
-
cacheReadPer1M: 0.25
|
|
420
|
-
}
|
|
421
|
-
};
|
|
422
|
-
OPENAI_PROMPT_TIERS = {
|
|
423
|
-
"gpt-5.5": {
|
|
424
|
-
threshold: 272000,
|
|
425
|
-
inputMultiplier: 2,
|
|
426
|
-
outputMultiplier: 1.5,
|
|
427
|
-
cacheReadMultiplier: 2
|
|
428
|
-
},
|
|
429
|
-
"gpt-5.4-pro": {
|
|
430
|
-
threshold: 272000,
|
|
431
|
-
inputMultiplier: 2,
|
|
432
|
-
outputMultiplier: 1.5,
|
|
433
|
-
cacheReadMultiplier: 2
|
|
434
|
-
},
|
|
435
|
-
"gpt-5.4": {
|
|
436
|
-
threshold: 272000,
|
|
437
|
-
inputMultiplier: 2,
|
|
438
|
-
outputMultiplier: 1.5,
|
|
439
|
-
cacheReadMultiplier: 2
|
|
440
|
-
}
|
|
441
|
-
};
|
|
442
|
-
QWEN_PROMPT_TIERS = {
|
|
443
|
-
"qwen3.6-plus": {
|
|
444
|
-
threshold: 256000,
|
|
445
|
-
inputPer1M: 1.3,
|
|
446
|
-
outputPer1M: 3.9,
|
|
447
|
-
cacheReadPer1M: 0.13,
|
|
448
|
-
cacheWritePer1M: 1.625
|
|
449
|
-
},
|
|
450
|
-
"qwen3.6-flash": {
|
|
451
|
-
threshold: 256000,
|
|
452
|
-
inputPer1M: 1,
|
|
453
|
-
outputPer1M: 4,
|
|
454
|
-
cacheReadPer1M: 0.1,
|
|
455
|
-
cacheWritePer1M: 1.25
|
|
456
|
-
},
|
|
457
|
-
"qwen3.6-max-preview": {
|
|
458
|
-
threshold: 128000,
|
|
459
|
-
inputPer1M: 1.6,
|
|
460
|
-
outputPer1M: 9.6,
|
|
461
|
-
cacheReadPer1M: 0.16,
|
|
462
|
-
cacheWritePer1M: 2
|
|
463
|
-
}
|
|
464
|
-
};
|
|
465
|
-
MINIMAX_PROMPT_TIERS = {
|
|
466
|
-
"minimax/minimax-m1": {
|
|
467
|
-
threshold: Number.POSITIVE_INFINITY
|
|
468
|
-
},
|
|
469
|
-
"minimax-m1": {
|
|
470
|
-
threshold: 200000,
|
|
471
|
-
inputPer1M: 1.3
|
|
472
|
-
}
|
|
473
|
-
};
|
|
474
|
-
XAI_PROMPT_TIERS = {
|
|
475
|
-
"grok-4.3": {
|
|
476
|
-
threshold: 200000,
|
|
477
|
-
inputPer1M: 2.5,
|
|
478
|
-
outputPer1M: 5,
|
|
479
|
-
cacheReadPer1M: 0.4
|
|
480
|
-
},
|
|
481
|
-
"grok-latest": {
|
|
482
|
-
threshold: 200000,
|
|
483
|
-
inputPer1M: 2.5,
|
|
484
|
-
outputPer1M: 5,
|
|
485
|
-
cacheReadPer1M: 0.4
|
|
486
|
-
},
|
|
487
|
-
"grok-4.20": {
|
|
488
|
-
threshold: 200000,
|
|
489
|
-
inputPer1M: 2.5,
|
|
490
|
-
outputPer1M: 5,
|
|
491
|
-
cacheReadPer1M: 0.4
|
|
492
|
-
},
|
|
493
|
-
"grok-4-1-fast": {
|
|
494
|
-
threshold: 128000,
|
|
495
|
-
inputPer1M: 0.4,
|
|
496
|
-
outputPer1M: 1,
|
|
497
|
-
cacheReadPer1M: 0
|
|
498
|
-
},
|
|
499
|
-
"grok-4-fast": {
|
|
500
|
-
threshold: 128000,
|
|
501
|
-
inputPer1M: 0.4,
|
|
502
|
-
outputPer1M: 1,
|
|
503
|
-
cacheReadPer1M: 0
|
|
504
|
-
},
|
|
505
|
-
"grok-4": {
|
|
506
|
-
threshold: 128000,
|
|
507
|
-
inputPer1M: 6,
|
|
508
|
-
outputPer1M: 30,
|
|
509
|
-
cacheReadPer1M: 0
|
|
510
|
-
}
|
|
101
|
+
o3: { inputPer1M: 10, outputPer1M: 40, cacheReadPer1M: 2.5, cacheWritePer1M: 0 },
|
|
102
|
+
"o3-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.55, cacheWritePer1M: 0 },
|
|
103
|
+
"o4-mini": { inputPer1M: 1.1, outputPer1M: 4.4, cacheReadPer1M: 0.275, cacheWritePer1M: 0 }
|
|
511
104
|
};
|
|
512
105
|
});
|
|
513
106
|
|
|
514
107
|
// src/db/database.ts
|
|
515
108
|
import { SqliteAdapter as Database } from "@hasna/cloud";
|
|
516
109
|
import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
517
|
-
import { hostname } from "os";
|
|
518
110
|
import { homedir } from "os";
|
|
519
111
|
import { join } from "path";
|
|
520
|
-
function getMachineId() {
|
|
521
|
-
if (process.env["ECONOMY_MACHINE_ID"])
|
|
522
|
-
return process.env["ECONOMY_MACHINE_ID"];
|
|
523
|
-
const h = hostname().toLowerCase();
|
|
524
|
-
if (h.startsWith("spark") || h.startsWith("apple"))
|
|
525
|
-
return h.split(".")[0];
|
|
526
|
-
return h.split(".")[0];
|
|
527
|
-
}
|
|
528
112
|
function getDataDir() {
|
|
529
113
|
const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
|
|
530
114
|
const newDir = join(home, ".hasna", "economy");
|
|
@@ -557,7 +141,6 @@ function openDatabase(dbPath, skipSeed = false) {
|
|
|
557
141
|
}
|
|
558
142
|
const db = new Database(path);
|
|
559
143
|
db.exec("PRAGMA journal_mode = WAL");
|
|
560
|
-
db.exec("PRAGMA busy_timeout = 5000");
|
|
561
144
|
db.exec("PRAGMA foreign_keys = ON");
|
|
562
145
|
initSchema(db);
|
|
563
146
|
if (!skipSeed) {
|
|
@@ -576,13 +159,10 @@ function initSchema(db) {
|
|
|
576
159
|
output_tokens INTEGER DEFAULT 0,
|
|
577
160
|
cache_read_tokens INTEGER DEFAULT 0,
|
|
578
161
|
cache_create_tokens INTEGER DEFAULT 0,
|
|
579
|
-
cache_create_5m_tokens INTEGER DEFAULT 0,
|
|
580
|
-
cache_create_1h_tokens INTEGER DEFAULT 0,
|
|
581
162
|
cost_usd REAL NOT NULL DEFAULT 0,
|
|
582
163
|
duration_ms INTEGER DEFAULT 0,
|
|
583
164
|
timestamp TEXT NOT NULL,
|
|
584
|
-
source_request_id TEXT
|
|
585
|
-
machine_id TEXT DEFAULT ''
|
|
165
|
+
source_request_id TEXT
|
|
586
166
|
);
|
|
587
167
|
|
|
588
168
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
@@ -594,8 +174,7 @@ function initSchema(db) {
|
|
|
594
174
|
ended_at TEXT,
|
|
595
175
|
total_cost_usd REAL DEFAULT 0,
|
|
596
176
|
total_tokens INTEGER DEFAULT 0,
|
|
597
|
-
request_count INTEGER DEFAULT 0
|
|
598
|
-
machine_id TEXT DEFAULT ''
|
|
177
|
+
request_count INTEGER DEFAULT 0
|
|
599
178
|
);
|
|
600
179
|
|
|
601
180
|
CREATE TABLE IF NOT EXISTS projects (
|
|
@@ -648,8 +227,6 @@ function initSchema(db) {
|
|
|
648
227
|
output_per_1m REAL NOT NULL DEFAULT 0,
|
|
649
228
|
cache_read_per_1m REAL NOT NULL DEFAULT 0,
|
|
650
229
|
cache_write_per_1m REAL NOT NULL DEFAULT 0,
|
|
651
|
-
cache_write_1h_per_1m REAL NOT NULL DEFAULT 0,
|
|
652
|
-
cache_storage_per_1m_hour REAL NOT NULL DEFAULT 0,
|
|
653
230
|
updated_at TEXT NOT NULL
|
|
654
231
|
);
|
|
655
232
|
|
|
@@ -662,115 +239,6 @@ function initSchema(db) {
|
|
|
662
239
|
machine_id TEXT,
|
|
663
240
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
664
241
|
);
|
|
665
|
-
|
|
666
|
-
CREATE TABLE IF NOT EXISTS billing_daily (
|
|
667
|
-
date TEXT NOT NULL,
|
|
668
|
-
provider TEXT NOT NULL,
|
|
669
|
-
description TEXT DEFAULT '',
|
|
670
|
-
cost_usd REAL NOT NULL DEFAULT 0,
|
|
671
|
-
updated_at TEXT NOT NULL,
|
|
672
|
-
PRIMARY KEY (date, provider, description)
|
|
673
|
-
);
|
|
674
|
-
|
|
675
|
-
CREATE INDEX IF NOT EXISTS idx_billing_date ON billing_daily(date);
|
|
676
|
-
CREATE INDEX IF NOT EXISTS idx_billing_provider ON billing_daily(provider);
|
|
677
|
-
|
|
678
|
-
CREATE TABLE IF NOT EXISTS subscriptions (
|
|
679
|
-
id TEXT PRIMARY KEY,
|
|
680
|
-
agent TEXT,
|
|
681
|
-
provider TEXT NOT NULL,
|
|
682
|
-
plan TEXT NOT NULL,
|
|
683
|
-
monthly_fee_usd REAL NOT NULL DEFAULT 0,
|
|
684
|
-
included_usage_usd REAL NOT NULL DEFAULT 0,
|
|
685
|
-
billing_cycle_start TEXT,
|
|
686
|
-
reset_policy TEXT DEFAULT 'monthly',
|
|
687
|
-
active INTEGER NOT NULL DEFAULT 1,
|
|
688
|
-
created_at TEXT NOT NULL,
|
|
689
|
-
updated_at TEXT NOT NULL
|
|
690
|
-
);
|
|
691
|
-
|
|
692
|
-
CREATE TABLE IF NOT EXISTS usage_snapshots (
|
|
693
|
-
id TEXT PRIMARY KEY,
|
|
694
|
-
agent TEXT NOT NULL,
|
|
695
|
-
date TEXT NOT NULL,
|
|
696
|
-
metric TEXT NOT NULL,
|
|
697
|
-
value REAL NOT NULL DEFAULT 0,
|
|
698
|
-
unit TEXT DEFAULT '',
|
|
699
|
-
machine_id TEXT DEFAULT '',
|
|
700
|
-
updated_at TEXT NOT NULL
|
|
701
|
-
);
|
|
702
|
-
|
|
703
|
-
CREATE TABLE IF NOT EXISTS savings_daily (
|
|
704
|
-
date TEXT NOT NULL,
|
|
705
|
-
agent TEXT DEFAULT '',
|
|
706
|
-
api_equivalent_usd REAL NOT NULL DEFAULT 0,
|
|
707
|
-
subscription_fee_usd REAL NOT NULL DEFAULT 0,
|
|
708
|
-
included_consumed_usd REAL NOT NULL DEFAULT 0,
|
|
709
|
-
on_demand_usd REAL NOT NULL DEFAULT 0,
|
|
710
|
-
saved_usd REAL NOT NULL DEFAULT 0,
|
|
711
|
-
updated_at TEXT NOT NULL,
|
|
712
|
-
PRIMARY KEY (date, agent)
|
|
713
|
-
);
|
|
714
|
-
|
|
715
|
-
CREATE TABLE IF NOT EXISTS machines (
|
|
716
|
-
machine_id TEXT PRIMARY KEY,
|
|
717
|
-
hostname TEXT NOT NULL,
|
|
718
|
-
last_seen_at TEXT,
|
|
719
|
-
last_push_at TEXT,
|
|
720
|
-
last_pull_at TEXT,
|
|
721
|
-
economy_version TEXT,
|
|
722
|
-
updated_at TEXT NOT NULL
|
|
723
|
-
);
|
|
724
|
-
|
|
725
|
-
CREATE INDEX IF NOT EXISTS idx_usage_agent_date ON usage_snapshots(agent, date);
|
|
726
|
-
CREATE INDEX IF NOT EXISTS idx_savings_date ON savings_daily(date);
|
|
727
|
-
`);
|
|
728
|
-
const cols = db.prepare(`PRAGMA table_info(requests)`).all();
|
|
729
|
-
if (!cols.some((c) => c.name === "machine_id")) {
|
|
730
|
-
db.exec(`ALTER TABLE requests ADD COLUMN machine_id TEXT DEFAULT ''`);
|
|
731
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN machine_id TEXT DEFAULT ''`);
|
|
732
|
-
}
|
|
733
|
-
if (!cols.some((c) => c.name === "cache_create_5m_tokens")) {
|
|
734
|
-
db.exec(`ALTER TABLE requests ADD COLUMN cache_create_5m_tokens INTEGER DEFAULT 0`);
|
|
735
|
-
db.exec(`UPDATE requests SET cache_create_5m_tokens = cache_create_tokens WHERE cache_create_5m_tokens = 0`);
|
|
736
|
-
}
|
|
737
|
-
if (!cols.some((c) => c.name === "cache_create_1h_tokens")) {
|
|
738
|
-
db.exec(`ALTER TABLE requests ADD COLUMN cache_create_1h_tokens INTEGER DEFAULT 0`);
|
|
739
|
-
}
|
|
740
|
-
if (!cols.some((c) => c.name === "cost_basis")) {
|
|
741
|
-
db.exec(`ALTER TABLE requests ADD COLUMN cost_basis TEXT DEFAULT 'estimated'`);
|
|
742
|
-
}
|
|
743
|
-
if (!cols.some((c) => c.name === "attribution_tag")) {
|
|
744
|
-
db.exec(`ALTER TABLE requests ADD COLUMN attribution_tag TEXT DEFAULT ''`);
|
|
745
|
-
}
|
|
746
|
-
if (!cols.some((c) => c.name === "updated_at")) {
|
|
747
|
-
db.exec(`ALTER TABLE requests ADD COLUMN updated_at TEXT DEFAULT ''`);
|
|
748
|
-
db.exec(`UPDATE requests SET updated_at = timestamp WHERE updated_at = '' OR updated_at IS NULL`);
|
|
749
|
-
}
|
|
750
|
-
if (!cols.some((c) => c.name === "synced_at")) {
|
|
751
|
-
db.exec(`ALTER TABLE requests ADD COLUMN synced_at TEXT DEFAULT ''`);
|
|
752
|
-
}
|
|
753
|
-
const sessionCols = db.prepare(`PRAGMA table_info(sessions)`).all();
|
|
754
|
-
if (!sessionCols.some((c) => c.name === "attribution_tag")) {
|
|
755
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN attribution_tag TEXT DEFAULT ''`);
|
|
756
|
-
}
|
|
757
|
-
if (!sessionCols.some((c) => c.name === "updated_at")) {
|
|
758
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN updated_at TEXT DEFAULT ''`);
|
|
759
|
-
db.exec(`UPDATE sessions SET updated_at = started_at WHERE updated_at = '' OR updated_at IS NULL`);
|
|
760
|
-
}
|
|
761
|
-
if (!sessionCols.some((c) => c.name === "synced_at")) {
|
|
762
|
-
db.exec(`ALTER TABLE sessions ADD COLUMN synced_at TEXT DEFAULT ''`);
|
|
763
|
-
}
|
|
764
|
-
const pricingCols = db.prepare(`PRAGMA table_info(model_pricing)`).all();
|
|
765
|
-
if (!pricingCols.some((c) => c.name === "cache_write_1h_per_1m")) {
|
|
766
|
-
db.exec(`ALTER TABLE model_pricing ADD COLUMN cache_write_1h_per_1m REAL NOT NULL DEFAULT 0`);
|
|
767
|
-
}
|
|
768
|
-
if (!pricingCols.some((c) => c.name === "cache_storage_per_1m_hour")) {
|
|
769
|
-
db.exec(`ALTER TABLE model_pricing ADD COLUMN cache_storage_per_1m_hour REAL NOT NULL DEFAULT 0`);
|
|
770
|
-
}
|
|
771
|
-
db.exec(`
|
|
772
|
-
CREATE INDEX IF NOT EXISTS idx_requests_machine ON requests(machine_id);
|
|
773
|
-
CREATE INDEX IF NOT EXISTS idx_sessions_machine ON sessions(machine_id);
|
|
774
242
|
`);
|
|
775
243
|
}
|
|
776
244
|
function periodWhere(period) {
|
|
@@ -780,11 +248,11 @@ function periodWhere(period) {
|
|
|
780
248
|
case "yesterday":
|
|
781
249
|
return `DATE(timestamp) = DATE('now', '-1 day')`;
|
|
782
250
|
case "week":
|
|
783
|
-
return `timestamp >= DATE('now', '
|
|
251
|
+
return `timestamp >= DATE('now', '-7 days')`;
|
|
784
252
|
case "month":
|
|
785
|
-
return `timestamp >= DATE('now', '
|
|
253
|
+
return `timestamp >= DATE('now', '-30 days')`;
|
|
786
254
|
case "year":
|
|
787
|
-
return `timestamp >= DATE('now', '
|
|
255
|
+
return `timestamp >= DATE('now', '-365 days')`;
|
|
788
256
|
case "all":
|
|
789
257
|
return "1=1";
|
|
790
258
|
}
|
|
@@ -796,34 +264,31 @@ function sessionPeriodWhere(period) {
|
|
|
796
264
|
case "yesterday":
|
|
797
265
|
return `DATE(started_at) = DATE('now', '-1 day')`;
|
|
798
266
|
case "week":
|
|
799
|
-
return `started_at >= DATE('now', '
|
|
267
|
+
return `started_at >= DATE('now', '-7 days')`;
|
|
800
268
|
case "month":
|
|
801
|
-
return `started_at >= DATE('now', '
|
|
269
|
+
return `started_at >= DATE('now', '-30 days')`;
|
|
802
270
|
case "year":
|
|
803
|
-
return `started_at >= DATE('now', '
|
|
271
|
+
return `started_at >= DATE('now', '-365 days')`;
|
|
804
272
|
case "all":
|
|
805
273
|
return "1=1";
|
|
806
274
|
}
|
|
807
275
|
}
|
|
808
276
|
function upsertRequest(db, req) {
|
|
809
|
-
const now = req.updated_at ?? new Date().toISOString();
|
|
810
277
|
db.prepare(`
|
|
811
278
|
INSERT OR REPLACE INTO requests
|
|
812
279
|
(id, agent, session_id, model, input_tokens, output_tokens,
|
|
813
|
-
cache_read_tokens, cache_create_tokens,
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
`).run(req.id, req.agent, req.session_id, req.model, req.input_tokens, req.output_tokens, req.cache_read_tokens, req.cache_create_tokens, req.cache_create_5m_tokens ?? req.cache_create_tokens, req.cache_create_1h_tokens ?? 0, req.cost_usd, req.cost_basis ?? "estimated", req.duration_ms, req.timestamp, req.source_request_id, req.machine_id ?? "", req.attribution_tag ?? process.env["ECONOMY_TAG"] ?? "", now, req.synced_at ?? "");
|
|
280
|
+
cache_read_tokens, cache_create_tokens, cost_usd, duration_ms,
|
|
281
|
+
timestamp, source_request_id)
|
|
282
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
283
|
+
`).run(req.id, req.agent, req.session_id, req.model, req.input_tokens, req.output_tokens, req.cache_read_tokens, req.cache_create_tokens, req.cost_usd, req.duration_ms, req.timestamp, req.source_request_id);
|
|
818
284
|
}
|
|
819
285
|
function upsertSession(db, session) {
|
|
820
|
-
const now = session.updated_at ?? new Date().toISOString();
|
|
821
286
|
db.prepare(`
|
|
822
287
|
INSERT OR REPLACE INTO sessions
|
|
823
288
|
(id, agent, project_path, project_name, started_at, ended_at,
|
|
824
|
-
total_cost_usd, total_tokens, request_count
|
|
825
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?,
|
|
826
|
-
`).run(session.id, session.agent, session.project_path, session.project_name, session.started_at, session.ended_at ?? null, session.total_cost_usd, session.total_tokens, session.request_count
|
|
289
|
+
total_cost_usd, total_tokens, request_count)
|
|
290
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
291
|
+
`).run(session.id, session.agent, session.project_path, session.project_name, session.started_at, session.ended_at ?? null, session.total_cost_usd, session.total_tokens, session.request_count);
|
|
827
292
|
}
|
|
828
293
|
function rollupSession(db, sessionId) {
|
|
829
294
|
db.prepare(`
|
|
@@ -853,10 +318,6 @@ function querySessions(db, filter = {}) {
|
|
|
853
318
|
conditions.push("started_at >= ?");
|
|
854
319
|
params.push(filter.since);
|
|
855
320
|
}
|
|
856
|
-
if (filter.machine) {
|
|
857
|
-
conditions.push("machine_id = ?");
|
|
858
|
-
params.push(filter.machine);
|
|
859
|
-
}
|
|
860
321
|
if (filter.search) {
|
|
861
322
|
const q = `%${filter.search}%`;
|
|
862
323
|
conditions.push("(project_name LIKE ? OR agent LIKE ? OR id LIKE ?)");
|
|
@@ -875,25 +336,24 @@ function queryTopSessions(db, n = 10, agent) {
|
|
|
875
336
|
}
|
|
876
337
|
return db.prepare(`SELECT * FROM sessions ORDER BY total_cost_usd DESC LIMIT ?`).all(n);
|
|
877
338
|
}
|
|
878
|
-
function querySummary(db, period
|
|
339
|
+
function querySummary(db, period) {
|
|
879
340
|
const rWhere = periodWhere(period);
|
|
880
341
|
const sWhere = sessionPeriodWhere(period);
|
|
881
|
-
const machineClause = !allMachines && machine ? ` AND machine_id = '${machine.replace(/'/g, "''")}'` : "";
|
|
882
342
|
const r = db.prepare(`
|
|
883
343
|
SELECT COALESCE(SUM(cost_usd), 0) as total_usd,
|
|
884
344
|
COUNT(*) as requests,
|
|
885
345
|
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as tokens
|
|
886
|
-
FROM requests WHERE ${rWhere}
|
|
346
|
+
FROM requests WHERE ${rWhere}
|
|
887
347
|
`).get();
|
|
888
348
|
const codexTotals = db.prepare(`
|
|
889
349
|
SELECT COALESCE(SUM(total_cost_usd), 0) as cost_usd,
|
|
890
350
|
COALESCE(SUM(total_tokens), 0) as tokens,
|
|
891
351
|
COUNT(*) as sessions
|
|
892
352
|
FROM sessions
|
|
893
|
-
WHERE ${sWhere}
|
|
353
|
+
WHERE ${sWhere}
|
|
894
354
|
AND id NOT IN (SELECT DISTINCT session_id FROM requests)
|
|
895
355
|
`).get();
|
|
896
|
-
const sessionCount = db.prepare(`SELECT COUNT(*) as sessions FROM sessions WHERE ${sWhere}
|
|
356
|
+
const sessionCount = db.prepare(`SELECT COUNT(*) as sessions FROM sessions WHERE ${sWhere}`).get();
|
|
897
357
|
return {
|
|
898
358
|
total_usd: r.total_usd + codexTotals.cost_usd,
|
|
899
359
|
requests: r.requests,
|
|
@@ -913,66 +373,23 @@ function queryModelBreakdown(db) {
|
|
|
913
373
|
FROM requests GROUP BY model, agent ORDER BY cost_usd DESC
|
|
914
374
|
`).all();
|
|
915
375
|
}
|
|
916
|
-
function labelForPath(projectPath, projectName) {
|
|
917
|
-
if (projectName && projectName.trim() !== "")
|
|
918
|
-
return projectName;
|
|
919
|
-
if (!projectPath)
|
|
920
|
-
return "";
|
|
921
|
-
const segments = projectPath.split("/").filter(Boolean);
|
|
922
|
-
const projectPrefix = /^(open|skill|hook|service|connect|platform|agent|tool|iapp|project|scaffold|capp)-/;
|
|
923
|
-
for (const seg of segments) {
|
|
924
|
-
if (projectPrefix.test(seg))
|
|
925
|
-
return seg;
|
|
926
|
-
}
|
|
927
|
-
const generic = new Set(["web", "app", "apps", "packages", "src", "lib", "server", "client", "api", "frontend", "backend"]);
|
|
928
|
-
for (let i = segments.length - 1;i >= 0; i--) {
|
|
929
|
-
if (!generic.has(segments[i].toLowerCase()))
|
|
930
|
-
return segments[i];
|
|
931
|
-
}
|
|
932
|
-
return segments[segments.length - 1] ?? projectPath;
|
|
933
|
-
}
|
|
934
376
|
function queryProjectBreakdown(db) {
|
|
935
|
-
|
|
936
|
-
SELECT
|
|
937
|
-
|
|
938
|
-
|
|
377
|
+
return db.prepare(`
|
|
378
|
+
SELECT
|
|
379
|
+
s.project_path,
|
|
380
|
+
COALESCE(p.name, s.project_name) as project_name,
|
|
381
|
+
COUNT(DISTINCT s.id) as sessions,
|
|
382
|
+
COUNT(r.id) as requests,
|
|
383
|
+
COALESCE(SUM(r.cost_usd), COALESCE(SUM(s.total_cost_usd), 0)) as cost_usd,
|
|
384
|
+
COALESCE(SUM(r.input_tokens + r.output_tokens + r.cache_read_tokens + r.cache_create_tokens), 0) as total_tokens,
|
|
385
|
+
MAX(s.started_at) as last_active
|
|
386
|
+
FROM sessions s
|
|
387
|
+
LEFT JOIN projects p ON p.path = s.project_path OR p.name = s.project_name
|
|
388
|
+
LEFT JOIN requests r ON r.session_id = s.id
|
|
389
|
+
WHERE s.project_path != '' OR s.project_name != ''
|
|
390
|
+
GROUP BY s.project_path
|
|
391
|
+
ORDER BY cost_usd DESC
|
|
939
392
|
`).all();
|
|
940
|
-
const groups = new Map;
|
|
941
|
-
for (const s of sessions) {
|
|
942
|
-
const label = labelForPath(s.project_path, s.project_name);
|
|
943
|
-
if (!label)
|
|
944
|
-
continue;
|
|
945
|
-
const g = groups.get(label) ?? { sessionIds: [], samplePath: s.project_path, totalCost: 0, lastActive: "" };
|
|
946
|
-
g.sessionIds.push(s.id);
|
|
947
|
-
g.totalCost += s.total_cost_usd || 0;
|
|
948
|
-
if (!g.lastActive || s.started_at > g.lastActive)
|
|
949
|
-
g.lastActive = s.started_at;
|
|
950
|
-
if (!g.samplePath)
|
|
951
|
-
g.samplePath = s.project_path;
|
|
952
|
-
groups.set(label, g);
|
|
953
|
-
}
|
|
954
|
-
const result = [];
|
|
955
|
-
for (const [label, g] of groups.entries()) {
|
|
956
|
-
const placeholders = g.sessionIds.map(() => "?").join(",");
|
|
957
|
-
const reqStats = placeholders.length ? db.prepare(`
|
|
958
|
-
SELECT
|
|
959
|
-
COUNT(*) as requests,
|
|
960
|
-
COALESCE(SUM(cost_usd), 0) as cost_usd,
|
|
961
|
-
COALESCE(SUM(input_tokens + output_tokens + cache_read_tokens + cache_create_tokens), 0) as total_tokens
|
|
962
|
-
FROM requests WHERE session_id IN (${placeholders})
|
|
963
|
-
`).get(...g.sessionIds) : { requests: 0, cost_usd: 0, total_tokens: 0 };
|
|
964
|
-
result.push({
|
|
965
|
-
project_path: g.samplePath,
|
|
966
|
-
project_name: label,
|
|
967
|
-
sessions: g.sessionIds.length,
|
|
968
|
-
requests: reqStats.requests,
|
|
969
|
-
total_tokens: reqStats.total_tokens,
|
|
970
|
-
cost_usd: reqStats.cost_usd > 0 ? reqStats.cost_usd : g.totalCost,
|
|
971
|
-
last_active: g.lastActive
|
|
972
|
-
});
|
|
973
|
-
}
|
|
974
|
-
result.sort((a, b) => b.cost_usd - a.cost_usd);
|
|
975
|
-
return result;
|
|
976
393
|
}
|
|
977
394
|
function queryDailyBreakdown(db, days = 30) {
|
|
978
395
|
return db.prepare(`
|
|
@@ -1090,46 +507,12 @@ function setIngestState(db, source, key, value) {
|
|
|
1090
507
|
function queryRequestsSince(db, since) {
|
|
1091
508
|
return db.prepare(`SELECT * FROM requests WHERE timestamp > ? ORDER BY timestamp ASC`).all(since);
|
|
1092
509
|
}
|
|
1093
|
-
function upsertBillingDaily(db, row) {
|
|
1094
|
-
db.prepare(`
|
|
1095
|
-
INSERT OR REPLACE INTO billing_daily (date, provider, description, cost_usd, updated_at)
|
|
1096
|
-
VALUES (?, ?, ?, ?, ?)
|
|
1097
|
-
`).run(row.date, row.provider, row.description, row.cost_usd, row.updated_at);
|
|
1098
|
-
}
|
|
1099
|
-
function clearBillingRange(db, provider, fromDate, toDate) {
|
|
1100
|
-
db.prepare(`DELETE FROM billing_daily WHERE provider = ? AND date >= ? AND date <= ?`).run(provider, fromDate, toDate);
|
|
1101
|
-
}
|
|
1102
|
-
function queryBillingSummary(db, period) {
|
|
1103
|
-
const where = period === "today" ? `date = DATE('now')` : period === "yesterday" ? `date = DATE('now', '-1 day')` : period === "week" ? `date >= DATE('now', 'weekday 0', '-7 days')` : period === "month" ? `date >= DATE('now', 'start of month')` : period === "year" ? `date >= DATE('now', 'start of year')` : "1=1";
|
|
1104
|
-
const rows = db.prepare(`SELECT provider, SUM(cost_usd) as cost FROM billing_daily WHERE ${where} GROUP BY provider`).all();
|
|
1105
|
-
const by_provider = {};
|
|
1106
|
-
let total = 0;
|
|
1107
|
-
for (const r of rows) {
|
|
1108
|
-
by_provider[r.provider] = r.cost;
|
|
1109
|
-
total += r.cost;
|
|
1110
|
-
}
|
|
1111
|
-
return { total_usd: total, by_provider };
|
|
1112
|
-
}
|
|
1113
|
-
function listMachines(db) {
|
|
1114
|
-
return db.prepare(`
|
|
1115
|
-
SELECT
|
|
1116
|
-
s.machine_id,
|
|
1117
|
-
COUNT(DISTINCT s.id) as sessions,
|
|
1118
|
-
COALESCE((SELECT COUNT(*) FROM requests r WHERE r.machine_id = s.machine_id), 0) as requests,
|
|
1119
|
-
COALESCE(SUM(s.total_cost_usd), 0) as total_cost_usd,
|
|
1120
|
-
MAX(s.started_at) as last_active
|
|
1121
|
-
FROM sessions s
|
|
1122
|
-
WHERE s.machine_id != ''
|
|
1123
|
-
GROUP BY s.machine_id
|
|
1124
|
-
ORDER BY total_cost_usd DESC
|
|
1125
|
-
`).all();
|
|
1126
|
-
}
|
|
1127
510
|
function upsertModelPricing(db, p) {
|
|
1128
511
|
db.prepare(`
|
|
1129
512
|
INSERT OR REPLACE INTO model_pricing
|
|
1130
|
-
(model, input_per_1m, output_per_1m, cache_read_per_1m, cache_write_per_1m,
|
|
1131
|
-
VALUES (?, ?, ?, ?, ?,
|
|
1132
|
-
`).run(p.model, p.input_per_1m, p.output_per_1m, p.cache_read_per_1m, p.cache_write_per_1m, p.
|
|
513
|
+
(model, input_per_1m, output_per_1m, cache_read_per_1m, cache_write_per_1m, updated_at)
|
|
514
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
515
|
+
`).run(p.model, p.input_per_1m, p.output_per_1m, p.cache_read_per_1m, p.cache_write_per_1m, p.updated_at);
|
|
1133
516
|
}
|
|
1134
517
|
function getModelPricing(db, model) {
|
|
1135
518
|
return db.prepare(`SELECT * FROM model_pricing WHERE model = ?`).get(model);
|
|
@@ -1141,104 +524,23 @@ function deleteModelPricing(db, model) {
|
|
|
1141
524
|
db.prepare(`DELETE FROM model_pricing WHERE model = ?`).run(model);
|
|
1142
525
|
}
|
|
1143
526
|
function seedModelPricing(db, defaults) {
|
|
1144
|
-
const existing =
|
|
527
|
+
const existing = db.prepare(`SELECT COUNT(*) as count FROM model_pricing`).get();
|
|
528
|
+
if (existing.count > 0)
|
|
529
|
+
return;
|
|
1145
530
|
const now = new Date().toISOString();
|
|
1146
531
|
for (const [model, p] of Object.entries(defaults)) {
|
|
1147
|
-
if (existing.has(model))
|
|
1148
|
-
continue;
|
|
1149
532
|
upsertModelPricing(db, {
|
|
1150
533
|
model,
|
|
1151
534
|
input_per_1m: p.inputPer1M,
|
|
1152
535
|
output_per_1m: p.outputPer1M,
|
|
1153
536
|
cache_read_per_1m: p.cacheReadPer1M,
|
|
1154
537
|
cache_write_per_1m: p.cacheWritePer1M,
|
|
1155
|
-
cache_write_1h_per_1m: p.cacheWrite1hPer1M ?? 0,
|
|
1156
|
-
cache_storage_per_1m_hour: p.cacheStoragePer1MHour ?? 0,
|
|
1157
538
|
updated_at: now
|
|
1158
539
|
});
|
|
1159
540
|
}
|
|
1160
541
|
}
|
|
1161
|
-
function upsertSubscription(db, sub) {
|
|
1162
|
-
db.prepare(`
|
|
1163
|
-
INSERT OR REPLACE INTO subscriptions
|
|
1164
|
-
(id, agent, provider, plan, monthly_fee_usd, included_usage_usd, billing_cycle_start, reset_policy, active, created_at, updated_at)
|
|
1165
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1166
|
-
`).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);
|
|
1167
|
-
}
|
|
1168
|
-
function listSubscriptions(db) {
|
|
1169
|
-
return db.prepare(`SELECT * FROM subscriptions ORDER BY provider, plan`).all();
|
|
1170
|
-
}
|
|
1171
|
-
function deleteSubscription(db, id) {
|
|
1172
|
-
db.prepare(`DELETE FROM subscriptions WHERE id = ?`).run(id);
|
|
1173
|
-
}
|
|
1174
|
-
function upsertUsageSnapshot(db, snap) {
|
|
1175
|
-
const now = snap.updated_at ?? new Date().toISOString();
|
|
1176
|
-
const id = snap.id ?? `${snap.agent}-${snap.date}-${snap.metric}-${snap.machine_id}`;
|
|
1177
|
-
db.prepare(`
|
|
1178
|
-
INSERT OR REPLACE INTO usage_snapshots (id, agent, date, metric, value, unit, machine_id, updated_at)
|
|
1179
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
1180
|
-
`).run(id, snap.agent, snap.date, snap.metric, snap.value, snap.unit, snap.machine_id, now);
|
|
1181
|
-
}
|
|
1182
|
-
function queryUsageSnapshots(db, opts = {}) {
|
|
1183
|
-
const conditions = [];
|
|
1184
|
-
const params = [];
|
|
1185
|
-
if (opts.agent) {
|
|
1186
|
-
conditions.push("agent = ?");
|
|
1187
|
-
params.push(opts.agent);
|
|
1188
|
-
}
|
|
1189
|
-
if (opts.date) {
|
|
1190
|
-
conditions.push("date = ?");
|
|
1191
|
-
params.push(opts.date);
|
|
1192
|
-
}
|
|
1193
|
-
if (opts.since) {
|
|
1194
|
-
conditions.push("date >= ?");
|
|
1195
|
-
params.push(opts.since);
|
|
1196
|
-
}
|
|
1197
|
-
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
1198
|
-
return db.prepare(`SELECT * FROM usage_snapshots ${where} ORDER BY date DESC, agent, metric`).all(...params);
|
|
1199
|
-
}
|
|
1200
|
-
function listMachineRegistry(db) {
|
|
1201
|
-
return db.prepare(`SELECT * FROM machines ORDER BY last_seen_at DESC`).all();
|
|
1202
|
-
}
|
|
1203
|
-
function dedupeRequests(db) {
|
|
1204
|
-
const dupes = db.prepare(`
|
|
1205
|
-
SELECT source_request_id, agent, MIN(id) as keep_id, COUNT(*) as cnt
|
|
1206
|
-
FROM requests
|
|
1207
|
-
WHERE source_request_id != '' AND source_request_id IS NOT NULL
|
|
1208
|
-
GROUP BY source_request_id, agent
|
|
1209
|
-
HAVING cnt > 1
|
|
1210
|
-
`).all();
|
|
1211
|
-
let removed = 0;
|
|
1212
|
-
for (const row of dupes) {
|
|
1213
|
-
const result = db.prepare(`
|
|
1214
|
-
DELETE FROM requests WHERE source_request_id = ? AND agent = ? AND id != ?
|
|
1215
|
-
`).run(row.source_request_id, row.agent, row.keep_id);
|
|
1216
|
-
removed += result.changes;
|
|
1217
|
-
}
|
|
1218
|
-
return removed;
|
|
1219
|
-
}
|
|
1220
542
|
var init_database = () => {};
|
|
1221
543
|
|
|
1222
|
-
// src/lib/agents.ts
|
|
1223
|
-
var AGENTS = [
|
|
1224
|
-
"claude",
|
|
1225
|
-
"takumi",
|
|
1226
|
-
"codex",
|
|
1227
|
-
"gemini",
|
|
1228
|
-
"opencode",
|
|
1229
|
-
"cursor",
|
|
1230
|
-
"pi",
|
|
1231
|
-
"hermes"
|
|
1232
|
-
];
|
|
1233
|
-
var COST_BASIS = [
|
|
1234
|
-
"metered_api",
|
|
1235
|
-
"subscription_included",
|
|
1236
|
-
"estimated",
|
|
1237
|
-
"unknown"
|
|
1238
|
-
];
|
|
1239
|
-
function isAgent(value) {
|
|
1240
|
-
return AGENTS.includes(value);
|
|
1241
|
-
}
|
|
1242
544
|
// src/index.ts
|
|
1243
545
|
init_database();
|
|
1244
546
|
init_pricing();
|
|
@@ -1246,9 +548,6 @@ init_pricing();
|
|
|
1246
548
|
// src/lib/gatherer.ts
|
|
1247
549
|
init_database();
|
|
1248
550
|
var SYSTEM_PROMPT = "You are a cost-aware AI assistant that tracks API usage, identifies expensive patterns, and helps optimize spending.";
|
|
1249
|
-
function hasCostData(summary) {
|
|
1250
|
-
return summary.total_usd > 0 || summary.sessions > 0 || summary.requests > 0 || summary.tokens > 0;
|
|
1251
|
-
}
|
|
1252
551
|
var gatherTrainingData = async (options = {}) => {
|
|
1253
552
|
const limit = options.limit ?? 500;
|
|
1254
553
|
const examples = [];
|
|
@@ -1258,8 +557,6 @@ var gatherTrainingData = async (options = {}) => {
|
|
|
1258
557
|
for (const period of periods) {
|
|
1259
558
|
try {
|
|
1260
559
|
const s = querySummary(db, period);
|
|
1261
|
-
if (!hasCostData(s))
|
|
1262
|
-
continue;
|
|
1263
560
|
examples.push({
|
|
1264
561
|
messages: [
|
|
1265
562
|
{ role: "system", content: SYSTEM_PROMPT },
|
|
@@ -1432,26 +729,22 @@ ${goals.map((g) => `- ${g.period} goal (${g.project_path ?? g.agent ?? "global"}
|
|
|
1432
729
|
// src/lib/model-config.ts
|
|
1433
730
|
init_database();
|
|
1434
731
|
import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync as mkdirSync2 } from "fs";
|
|
1435
|
-
import {
|
|
732
|
+
import { join as join2 } from "path";
|
|
1436
733
|
var DEFAULT_MODEL = "gpt-4o-mini";
|
|
1437
|
-
|
|
1438
|
-
return process.env["HASNA_ECONOMY_CONFIG_PATH"] ?? join2(getDataDir(), "config.json");
|
|
1439
|
-
}
|
|
734
|
+
var CONFIG_PATH = join2(getDataDir(), "config.json");
|
|
1440
735
|
function loadConfig() {
|
|
1441
736
|
try {
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
return JSON.parse(readFileSync(configPath, "utf-8"));
|
|
737
|
+
if (existsSync2(CONFIG_PATH)) {
|
|
738
|
+
return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
|
|
1445
739
|
}
|
|
1446
740
|
} catch {}
|
|
1447
741
|
return {};
|
|
1448
742
|
}
|
|
1449
743
|
function saveConfig(config) {
|
|
1450
|
-
const
|
|
1451
|
-
const dir = dirname(configPath);
|
|
744
|
+
const dir = getDataDir();
|
|
1452
745
|
if (!existsSync2(dir))
|
|
1453
746
|
mkdirSync2(dir, { recursive: true });
|
|
1454
|
-
writeFileSync(
|
|
747
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + `
|
|
1455
748
|
`);
|
|
1456
749
|
}
|
|
1457
750
|
function getActiveModel() {
|
|
@@ -1467,56 +760,16 @@ function clearActiveModel() {
|
|
|
1467
760
|
delete config.activeModel;
|
|
1468
761
|
saveConfig(config);
|
|
1469
762
|
}
|
|
1470
|
-
// src/lib/open-projects.ts
|
|
1471
|
-
init_database();
|
|
1472
|
-
async function syncOpenProjectsRegistry(db, listActiveProjects) {
|
|
1473
|
-
let listProjects2 = listActiveProjects;
|
|
1474
|
-
if (!listProjects2) {
|
|
1475
|
-
const projectsApi = await import("@hasna/projects");
|
|
1476
|
-
listProjects2 = projectsApi.listProjects;
|
|
1477
|
-
}
|
|
1478
|
-
const projects = listProjects2({ status: "active", limit: 5000 });
|
|
1479
|
-
let imported = 0;
|
|
1480
|
-
let skipped = 0;
|
|
1481
|
-
for (const project of projects) {
|
|
1482
|
-
if (!project.path) {
|
|
1483
|
-
skipped++;
|
|
1484
|
-
continue;
|
|
1485
|
-
}
|
|
1486
|
-
upsertProject(db, {
|
|
1487
|
-
id: project.id,
|
|
1488
|
-
path: project.path,
|
|
1489
|
-
name: project.name,
|
|
1490
|
-
description: project.description,
|
|
1491
|
-
tags: project.tags ?? [],
|
|
1492
|
-
created_at: project.created_at
|
|
1493
|
-
});
|
|
1494
|
-
imported++;
|
|
1495
|
-
}
|
|
1496
|
-
return { imported, skipped };
|
|
1497
|
-
}
|
|
1498
763
|
// src/ingest/claude.ts
|
|
1499
764
|
init_database();
|
|
1500
765
|
init_pricing();
|
|
1501
766
|
import { readdirSync as readdirSync2, readFileSync as readFileSync2, existsSync as existsSync3, statSync as statSync2 } from "fs";
|
|
1502
767
|
import { homedir as homedir2 } from "os";
|
|
1503
768
|
import { join as join3, basename } from "path";
|
|
1504
|
-
|
|
1505
|
-
// src/lib/savings.ts
|
|
1506
|
-
function defaultCostBasisForAgent(agent) {
|
|
1507
|
-
if (agent === "claude")
|
|
1508
|
-
return "metered_api";
|
|
1509
|
-
if (agent === "cursor")
|
|
1510
|
-
return "subscription_included";
|
|
1511
|
-
return "estimated";
|
|
1512
|
-
}
|
|
1513
|
-
|
|
1514
|
-
// src/ingest/claude.ts
|
|
1515
769
|
function autoDetectProject(cwd, projects) {
|
|
1516
770
|
return projects.find((p) => cwd === p.path || cwd.startsWith(p.path + "/"));
|
|
1517
771
|
}
|
|
1518
|
-
var
|
|
1519
|
-
var TAKUMI_PROJECTS_DIR = join3(homedir2(), ".takumi", "projects");
|
|
772
|
+
var PROJECTS_DIR = join3(homedir2(), ".claude", "projects");
|
|
1520
773
|
function dirNameToPath(dirName) {
|
|
1521
774
|
return dirName.replace(/^-/, "/").replace(/-/g, "/").replace(/\/\//g, "/-");
|
|
1522
775
|
}
|
|
@@ -1535,37 +788,30 @@ function collectJsonlFiles(projectDir) {
|
|
|
1535
788
|
walk(projectDir);
|
|
1536
789
|
return files;
|
|
1537
790
|
}
|
|
1538
|
-
async function ingestClaude(db, verbose = false,
|
|
1539
|
-
|
|
1540
|
-
}
|
|
1541
|
-
async function ingestTakumi(db, verbose = false, projectsDir = TAKUMI_PROJECTS_DIR) {
|
|
1542
|
-
return ingestJsonlProjects(db, projectsDir, "takumi", verbose);
|
|
1543
|
-
}
|
|
1544
|
-
async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false) {
|
|
1545
|
-
if (!existsSync3(projectsDir)) {
|
|
791
|
+
async function ingestClaude(db, verbose = false, _telemetryDir) {
|
|
792
|
+
if (!existsSync3(PROJECTS_DIR)) {
|
|
1546
793
|
if (verbose)
|
|
1547
|
-
console.log(
|
|
794
|
+
console.log("Claude projects dir not found:", PROJECTS_DIR);
|
|
1548
795
|
return { files: 0, requests: 0, sessions: 0 };
|
|
1549
796
|
}
|
|
1550
|
-
const machineId = getMachineId();
|
|
1551
797
|
let totalFiles = 0;
|
|
1552
798
|
let totalRequests = 0;
|
|
1553
799
|
const touchedSessions = new Set;
|
|
1554
800
|
const registeredProjects = db.prepare(`SELECT path, name FROM projects ORDER BY LENGTH(path) DESC`).all();
|
|
1555
|
-
const projectDirs = readdirSync2(
|
|
801
|
+
const projectDirs = readdirSync2(PROJECTS_DIR, { withFileTypes: true }).filter((d) => d.isDirectory());
|
|
1556
802
|
for (const projectDirEntry of projectDirs) {
|
|
1557
|
-
const projectDirPath = join3(
|
|
803
|
+
const projectDirPath = join3(PROJECTS_DIR, projectDirEntry.name);
|
|
1558
804
|
const projectPath = dirNameToPath(projectDirEntry.name);
|
|
1559
805
|
const jsonlFiles = collectJsonlFiles(projectDirPath);
|
|
1560
806
|
for (const filePath of jsonlFiles) {
|
|
1561
|
-
const stateKey = filePath.replace(
|
|
807
|
+
const stateKey = filePath.replace(PROJECTS_DIR, "");
|
|
1562
808
|
let fileMtime = "0";
|
|
1563
809
|
try {
|
|
1564
810
|
fileMtime = statSync2(filePath).mtimeMs.toString();
|
|
1565
811
|
} catch {
|
|
1566
812
|
continue;
|
|
1567
813
|
}
|
|
1568
|
-
const processed = getIngestState(db,
|
|
814
|
+
const processed = getIngestState(db, "claude", stateKey);
|
|
1569
815
|
if (processed === fileMtime)
|
|
1570
816
|
continue;
|
|
1571
817
|
let lines;
|
|
@@ -1600,38 +846,26 @@ async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false)
|
|
|
1600
846
|
continue;
|
|
1601
847
|
const inputTokens = usage.input_tokens ?? 0;
|
|
1602
848
|
const outputTokens = usage.output_tokens ?? 0;
|
|
1603
|
-
const
|
|
1604
|
-
const cacheWrite1hTokens = usage.cache_creation?.ephemeral_1h_input_tokens ?? 0;
|
|
1605
|
-
const cacheWriteTokens = cacheWrite5mTokens + cacheWrite1hTokens;
|
|
849
|
+
const cacheWriteTokens = usage.cache_creation_input_tokens ?? 0;
|
|
1606
850
|
const cacheReadTokens = usage.cache_read_input_tokens ?? 0;
|
|
1607
851
|
const timestamp = entry.timestamp ?? new Date().toISOString();
|
|
1608
|
-
if (inputTokens + outputTokens + cacheWriteTokens
|
|
852
|
+
if (inputTokens + outputTokens + cacheWriteTokens === 0)
|
|
1609
853
|
continue;
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
const serverToolUse = usage.server_tool_use;
|
|
1613
|
-
if (serverToolUse?.web_search_requests) {
|
|
1614
|
-
costUsd += serverToolUse.web_search_requests * 0.01;
|
|
1615
|
-
}
|
|
1616
|
-
const sourceRequestId = entry.requestId ?? entry.request_id ?? entry.message.id ?? entry.uuid ?? `${sessionId}-${timestamp}`;
|
|
1617
|
-
const reqId = `${agentName}-${sourceRequestId}`;
|
|
854
|
+
const costUsd = computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens);
|
|
855
|
+
const reqId = `claude-${sessionId}-${timestamp}`;
|
|
1618
856
|
upsertRequest(db, {
|
|
1619
857
|
id: reqId,
|
|
1620
|
-
agent:
|
|
858
|
+
agent: "claude",
|
|
1621
859
|
session_id: sessionId,
|
|
1622
860
|
model,
|
|
1623
861
|
input_tokens: inputTokens,
|
|
1624
862
|
output_tokens: outputTokens,
|
|
1625
863
|
cache_read_tokens: cacheReadTokens,
|
|
1626
864
|
cache_create_tokens: cacheWriteTokens,
|
|
1627
|
-
cache_create_5m_tokens: cacheWrite5mTokens,
|
|
1628
|
-
cache_create_1h_tokens: cacheWrite1hTokens,
|
|
1629
865
|
cost_usd: costUsd,
|
|
1630
|
-
cost_basis: defaultCostBasisForAgent(agentName),
|
|
1631
866
|
duration_ms: 0,
|
|
1632
867
|
timestamp,
|
|
1633
|
-
source_request_id:
|
|
1634
|
-
machine_id: machineId
|
|
868
|
+
source_request_id: reqId
|
|
1635
869
|
});
|
|
1636
870
|
if (!touchedSessions.has(sessionId)) {
|
|
1637
871
|
const existing = db.prepare(`SELECT id FROM sessions WHERE id = ?`).get(sessionId);
|
|
@@ -1640,15 +874,14 @@ async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false)
|
|
|
1640
874
|
const detectedProject = autoDetectProject(effectiveCwd, registeredProjects);
|
|
1641
875
|
const session = {
|
|
1642
876
|
id: sessionId,
|
|
1643
|
-
agent:
|
|
877
|
+
agent: "claude",
|
|
1644
878
|
project_path: detectedProject ? detectedProject.path : effectiveCwd,
|
|
1645
879
|
project_name: detectedProject ? detectedProject.name : "",
|
|
1646
880
|
started_at: timestamp,
|
|
1647
881
|
ended_at: null,
|
|
1648
882
|
total_cost_usd: 0,
|
|
1649
883
|
total_tokens: 0,
|
|
1650
|
-
request_count: 0
|
|
1651
|
-
machine_id: machineId
|
|
884
|
+
request_count: 0
|
|
1652
885
|
};
|
|
1653
886
|
upsertSession(db, session);
|
|
1654
887
|
}
|
|
@@ -1656,7 +889,7 @@ async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false)
|
|
|
1656
889
|
}
|
|
1657
890
|
totalRequests++;
|
|
1658
891
|
}
|
|
1659
|
-
setIngestState(db,
|
|
892
|
+
setIngestState(db, "claude", stateKey, fileMtime);
|
|
1660
893
|
totalFiles++;
|
|
1661
894
|
}
|
|
1662
895
|
}
|
|
@@ -1665,365 +898,79 @@ async function ingestJsonlProjects(db, projectsDir, agentName, verbose = false)
|
|
|
1665
898
|
}
|
|
1666
899
|
return { files: totalFiles, requests: totalRequests, sessions: touchedSessions.size };
|
|
1667
900
|
}
|
|
1668
|
-
function applyClaudeModifiers(costUsd, model, usage, entry) {
|
|
1669
|
-
let multiplier = 1;
|
|
1670
|
-
const speed = usage.speed ?? entry.message?.speed ?? entry.speed;
|
|
1671
|
-
if (speed === "fast" && model.includes("opus-4-6")) {
|
|
1672
|
-
multiplier *= 6;
|
|
1673
|
-
}
|
|
1674
|
-
const inferenceGeo = usage.inference_geo ?? entry.message?.inference_geo ?? entry.inference_geo;
|
|
1675
|
-
if (inferenceGeo && ["us", "us-only", "us_only"].includes(inferenceGeo) && supportsClaudeDataResidencyPricing(model)) {
|
|
1676
|
-
multiplier *= 1.1;
|
|
1677
|
-
}
|
|
1678
|
-
return costUsd * multiplier;
|
|
1679
|
-
}
|
|
1680
|
-
function supportsClaudeDataResidencyPricing(model) {
|
|
1681
|
-
const normalized = normalizeModelName(model);
|
|
1682
|
-
const match = normalized.match(/^claude-(opus|sonnet|haiku)-(\d+)(?:-(\d+))?(?:-|$)/);
|
|
1683
|
-
if (!match)
|
|
1684
|
-
return false;
|
|
1685
|
-
const major = Number(match[2]);
|
|
1686
|
-
const minor = match[3] ? Number(match[3]) : 0;
|
|
1687
|
-
return major > 4 || major === 4 && minor >= 6;
|
|
1688
|
-
}
|
|
1689
901
|
// src/ingest/codex.ts
|
|
1690
902
|
init_database();
|
|
1691
|
-
init_pricing();
|
|
1692
903
|
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
1693
904
|
import { homedir as homedir3 } from "os";
|
|
1694
905
|
import { join as join4, basename as basename2 } from "path";
|
|
1695
|
-
import { Database as
|
|
1696
|
-
var
|
|
1697
|
-
var
|
|
1698
|
-
var CODEX_INGEST_VERSION = "rollout-token-dedupe-v2";
|
|
1699
|
-
function codexDbPath() {
|
|
1700
|
-
return process.env["HASNA_ECONOMY_CODEX_DB_PATH"] ?? DEFAULT_CODEX_DB_PATH;
|
|
1701
|
-
}
|
|
1702
|
-
function codexConfigPath() {
|
|
1703
|
-
return process.env["HASNA_ECONOMY_CODEX_CONFIG_PATH"] ?? DEFAULT_CODEX_CONFIG_PATH;
|
|
1704
|
-
}
|
|
906
|
+
import { Database as Database2 } from "bun:sqlite";
|
|
907
|
+
var CODEX_DB_PATH = join4(homedir3(), ".codex", "state_5.sqlite");
|
|
908
|
+
var CODEX_CONFIG_PATH = join4(homedir3(), ".codex", "config.toml");
|
|
1705
909
|
function readCodexModel() {
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
return "gpt-5-codex";
|
|
910
|
+
if (!existsSync4(CODEX_CONFIG_PATH))
|
|
911
|
+
return "gpt-5.3-codex";
|
|
1709
912
|
try {
|
|
1710
|
-
const content = readFileSync3(
|
|
913
|
+
const content = readFileSync3(CODEX_CONFIG_PATH, "utf-8");
|
|
1711
914
|
const match = content.match(/^model\s*=\s*"([^"]+)"/m);
|
|
1712
|
-
return match?.[1] ?? "gpt-5-codex";
|
|
915
|
+
return match?.[1] ?? "gpt-5.3-codex";
|
|
1713
916
|
} catch {
|
|
1714
|
-
return "gpt-5-codex";
|
|
1715
|
-
}
|
|
1716
|
-
}
|
|
1717
|
-
function buildThreadQuery(codexDb) {
|
|
1718
|
-
const cols = new Set(codexDb.prepare(`PRAGMA table_info(threads)`).all().map((c) => c.name));
|
|
1719
|
-
const modelSelect = cols.has("model") ? "model" : "NULL AS model";
|
|
1720
|
-
const rolloutSelect = cols.has("rollout_path") ? "rollout_path" : "NULL AS rollout_path";
|
|
1721
|
-
const providerSelect = cols.has("model_provider") ? "model_provider" : "NULL AS model_provider";
|
|
1722
|
-
return `
|
|
1723
|
-
SELECT id, ${rolloutSelect}, cwd, created_at, updated_at, tokens_used, title,
|
|
1724
|
-
${providerSelect}, ${modelSelect}
|
|
1725
|
-
FROM threads WHERE tokens_used > 0
|
|
1726
|
-
`;
|
|
1727
|
-
}
|
|
1728
|
-
function readTokenEvents(rolloutPath) {
|
|
1729
|
-
if (!rolloutPath || !existsSync4(rolloutPath))
|
|
1730
|
-
return [];
|
|
1731
|
-
const events = [];
|
|
1732
|
-
const seen = new Set;
|
|
1733
|
-
for (const line of readFileSync3(rolloutPath, "utf-8").split(`
|
|
1734
|
-
`)) {
|
|
1735
|
-
if (!line.trim())
|
|
1736
|
-
continue;
|
|
1737
|
-
let entry;
|
|
1738
|
-
try {
|
|
1739
|
-
entry = JSON.parse(line);
|
|
1740
|
-
} catch {
|
|
1741
|
-
continue;
|
|
1742
|
-
}
|
|
1743
|
-
if (!entry || typeof entry !== "object")
|
|
1744
|
-
continue;
|
|
1745
|
-
const payload = entry["payload"];
|
|
1746
|
-
if (!payload || payload["type"] !== "token_count")
|
|
1747
|
-
continue;
|
|
1748
|
-
const info = payload["info"];
|
|
1749
|
-
const usage = info?.["last_token_usage"];
|
|
1750
|
-
if (!usage)
|
|
1751
|
-
continue;
|
|
1752
|
-
const total = usage.total_tokens ?? (usage.input_tokens ?? 0) + (usage.output_tokens ?? 0);
|
|
1753
|
-
if (total <= 0)
|
|
1754
|
-
continue;
|
|
1755
|
-
const key = JSON.stringify(usage);
|
|
1756
|
-
if (seen.has(key))
|
|
1757
|
-
continue;
|
|
1758
|
-
seen.add(key);
|
|
1759
|
-
const timestamp = entry["timestamp"];
|
|
1760
|
-
events.push({ usage, timestamp: typeof timestamp === "string" ? timestamp : undefined });
|
|
917
|
+
return "gpt-5.3-codex";
|
|
1761
918
|
}
|
|
1762
|
-
return events;
|
|
1763
|
-
}
|
|
1764
|
-
function fallbackEvents(totalTokens) {
|
|
1765
|
-
const inputTokens = Math.floor(totalTokens * 0.6);
|
|
1766
|
-
return [{
|
|
1767
|
-
usage: {
|
|
1768
|
-
input_tokens: inputTokens,
|
|
1769
|
-
cached_input_tokens: 0,
|
|
1770
|
-
output_tokens: totalTokens - inputTokens,
|
|
1771
|
-
total_tokens: totalTokens
|
|
1772
|
-
}
|
|
1773
|
-
}];
|
|
1774
919
|
}
|
|
1775
920
|
async function ingestCodex(db, verbose = false) {
|
|
1776
|
-
|
|
1777
|
-
if (!existsSync4(dbPath)) {
|
|
921
|
+
if (!existsSync4(CODEX_DB_PATH)) {
|
|
1778
922
|
if (verbose)
|
|
1779
|
-
console.log("Codex DB not found:",
|
|
1780
|
-
return { sessions: 0
|
|
923
|
+
console.log("Codex DB not found:", CODEX_DB_PATH);
|
|
924
|
+
return { sessions: 0 };
|
|
1781
925
|
}
|
|
1782
|
-
const machineId = getMachineId();
|
|
1783
926
|
let codexDb = null;
|
|
1784
927
|
let ingested = 0;
|
|
1785
|
-
let requests = 0;
|
|
1786
928
|
try {
|
|
1787
|
-
codexDb = new
|
|
1788
|
-
const threads = codexDb.prepare(
|
|
929
|
+
codexDb = new Database2(CODEX_DB_PATH, { readonly: true });
|
|
930
|
+
const threads = codexDb.prepare(`SELECT id, cwd, created_at, updated_at, tokens_used, title FROM threads WHERE tokens_used > 0`).all();
|
|
1789
931
|
for (const thread of threads) {
|
|
1790
|
-
const
|
|
1791
|
-
const
|
|
1792
|
-
|
|
1793
|
-
if (processed === stateValue)
|
|
932
|
+
const stateKey = thread.id;
|
|
933
|
+
const processed = getIngestState(db, "codex", stateKey);
|
|
934
|
+
if (processed === "done")
|
|
1794
935
|
continue;
|
|
936
|
+
const costUsd = 0;
|
|
1795
937
|
const projectPath = thread.cwd ?? "";
|
|
1796
938
|
const projectName = projectPath ? basename2(projectPath) : "unknown";
|
|
1797
|
-
const sessionId = `codex-${thread.id}`;
|
|
1798
939
|
const startedAt = thread.created_at ? new Date(thread.created_at * 1000).toISOString() : new Date().toISOString();
|
|
1799
940
|
const endedAt = thread.updated_at ? new Date(thread.updated_at * 1000).toISOString() : null;
|
|
1800
941
|
upsertSession(db, {
|
|
1801
|
-
id:
|
|
942
|
+
id: `codex-${thread.id}`,
|
|
1802
943
|
agent: "codex",
|
|
1803
944
|
project_path: projectPath,
|
|
1804
945
|
project_name: projectName,
|
|
1805
946
|
started_at: startedAt,
|
|
1806
947
|
ended_at: endedAt,
|
|
1807
|
-
total_cost_usd:
|
|
1808
|
-
total_tokens:
|
|
1809
|
-
request_count:
|
|
1810
|
-
machine_id: machineId
|
|
948
|
+
total_cost_usd: costUsd,
|
|
949
|
+
total_tokens: thread.tokens_used,
|
|
950
|
+
request_count: 1
|
|
1811
951
|
});
|
|
1812
|
-
|
|
1813
|
-
const tokenEvents = events.length > 0 ? events : fallbackEvents(thread.tokens_used);
|
|
1814
|
-
db.prepare(`DELETE FROM requests WHERE session_id = ?`).run(sessionId);
|
|
1815
|
-
tokenEvents.forEach((event, index) => {
|
|
1816
|
-
const usage = event.usage;
|
|
1817
|
-
const inputTotal = usage.input_tokens ?? 0;
|
|
1818
|
-
const cacheReadTokens = usage.cached_input_tokens ?? 0;
|
|
1819
|
-
const inputTokens = Math.max(inputTotal - cacheReadTokens, 0);
|
|
1820
|
-
const outputTokens = usage.output_tokens ?? Math.max((usage.total_tokens ?? 0) - inputTotal, 0);
|
|
1821
|
-
const costUsd = computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens, 0);
|
|
1822
|
-
const timestamp = event.timestamp ?? (thread.created_at ? new Date(thread.created_at * 1000 + index).toISOString() : new Date().toISOString());
|
|
1823
|
-
const requestId = `${sessionId}-${index}`;
|
|
1824
|
-
upsertRequest(db, {
|
|
1825
|
-
id: requestId,
|
|
1826
|
-
agent: "codex",
|
|
1827
|
-
session_id: sessionId,
|
|
1828
|
-
model,
|
|
1829
|
-
input_tokens: inputTokens,
|
|
1830
|
-
output_tokens: outputTokens,
|
|
1831
|
-
cache_read_tokens: cacheReadTokens,
|
|
1832
|
-
cache_create_tokens: 0,
|
|
1833
|
-
cost_usd: costUsd,
|
|
1834
|
-
cost_basis: defaultCostBasisForAgent("codex"),
|
|
1835
|
-
duration_ms: 0,
|
|
1836
|
-
timestamp,
|
|
1837
|
-
source_request_id: requestId,
|
|
1838
|
-
machine_id: machineId
|
|
1839
|
-
});
|
|
1840
|
-
requests++;
|
|
1841
|
-
});
|
|
1842
|
-
rollupSession(db, sessionId);
|
|
1843
|
-
setIngestState(db, "codex", thread.id, stateValue);
|
|
952
|
+
setIngestState(db, "codex", stateKey, "done");
|
|
1844
953
|
ingested++;
|
|
1845
954
|
if (verbose)
|
|
1846
|
-
console.log(`Codex session ${thread.id}: ${thread.tokens_used} tokens
|
|
955
|
+
console.log(`Codex session ${thread.id}: ${thread.tokens_used} tokens \u2192 $${costUsd.toFixed(4)}`);
|
|
1847
956
|
}
|
|
1848
957
|
} finally {
|
|
1849
958
|
codexDb?.close();
|
|
1850
959
|
}
|
|
1851
|
-
return { sessions: ingested
|
|
1852
|
-
}
|
|
1853
|
-
// src/ingest/gemini.ts
|
|
1854
|
-
init_database();
|
|
1855
|
-
init_pricing();
|
|
1856
|
-
import { readdirSync as readdirSync3, readFileSync as readFileSync4, existsSync as existsSync5, statSync as statSync3 } from "fs";
|
|
1857
|
-
import { homedir as homedir4 } from "os";
|
|
1858
|
-
import { join as join5, basename as basename3 } from "path";
|
|
1859
|
-
var DEFAULT_GEMINI_TMP_DIR = join5(homedir4(), ".gemini", "tmp");
|
|
1860
|
-
var DEFAULT_GEMINI_HISTORY_DIR = join5(homedir4(), ".gemini", "history");
|
|
1861
|
-
function geminiTmpDir() {
|
|
1862
|
-
return process.env["HASNA_ECONOMY_GEMINI_TMP_DIR"] ?? DEFAULT_GEMINI_TMP_DIR;
|
|
1863
|
-
}
|
|
1864
|
-
function geminiHistoryDir() {
|
|
1865
|
-
return process.env["HASNA_ECONOMY_GEMINI_HISTORY_DIR"] ?? DEFAULT_GEMINI_HISTORY_DIR;
|
|
1866
|
-
}
|
|
1867
|
-
function numberField(...values) {
|
|
1868
|
-
for (const value of values) {
|
|
1869
|
-
if (typeof value === "number" && Number.isFinite(value))
|
|
1870
|
-
return value;
|
|
1871
|
-
}
|
|
1872
|
-
return 0;
|
|
1873
|
-
}
|
|
1874
|
-
function listProjectDirs(...roots) {
|
|
1875
|
-
const dirs = new Set;
|
|
1876
|
-
for (const root of roots) {
|
|
1877
|
-
if (!existsSync5(root))
|
|
1878
|
-
continue;
|
|
1879
|
-
try {
|
|
1880
|
-
for (const entry of readdirSync3(root, { withFileTypes: true })) {
|
|
1881
|
-
if (entry.isDirectory())
|
|
1882
|
-
dirs.add(join5(root, entry.name));
|
|
1883
|
-
}
|
|
1884
|
-
} catch {}
|
|
1885
|
-
}
|
|
1886
|
-
return [...dirs];
|
|
1887
|
-
}
|
|
1888
|
-
function projectRoot(projectDir, chatData) {
|
|
1889
|
-
if (chatData.projectPath)
|
|
1890
|
-
return chatData.projectPath;
|
|
1891
|
-
if (chatData.project_path)
|
|
1892
|
-
return chatData.project_path;
|
|
1893
|
-
const rootFile = join5(projectDir, ".project_root");
|
|
1894
|
-
try {
|
|
1895
|
-
if (existsSync5(rootFile))
|
|
1896
|
-
return readFileSync4(rootFile, "utf-8").trim();
|
|
1897
|
-
} catch {}
|
|
1898
|
-
return "";
|
|
1899
|
-
}
|
|
1900
|
-
async function ingestGemini(db, verbose) {
|
|
1901
|
-
const tmpDir = geminiTmpDir();
|
|
1902
|
-
const historyDir = geminiHistoryDir();
|
|
1903
|
-
if (!existsSync5(tmpDir) && !existsSync5(historyDir)) {
|
|
1904
|
-
if (verbose)
|
|
1905
|
-
console.log("Gemini tmp/history dirs not found:", tmpDir, historyDir);
|
|
1906
|
-
return { sessions: 0, requests: 0 };
|
|
1907
|
-
}
|
|
1908
|
-
const machineId = getMachineId();
|
|
1909
|
-
let totalSessions = 0;
|
|
1910
|
-
let totalRequests = 0;
|
|
1911
|
-
const touchedSessions = new Set;
|
|
1912
|
-
const projectDirs = listProjectDirs(tmpDir, historyDir);
|
|
1913
|
-
for (const projectDir of projectDirs) {
|
|
1914
|
-
const chatsDir = join5(projectDir, "chats");
|
|
1915
|
-
if (!existsSync5(chatsDir))
|
|
1916
|
-
continue;
|
|
1917
|
-
let chatFiles = [];
|
|
1918
|
-
try {
|
|
1919
|
-
chatFiles = readdirSync3(chatsDir).filter((f) => f.endsWith(".json")).map((f) => join5(chatsDir, f));
|
|
1920
|
-
} catch {
|
|
1921
|
-
continue;
|
|
1922
|
-
}
|
|
1923
|
-
for (const filePath of chatFiles) {
|
|
1924
|
-
const stateKey = filePath.replace(homedir4(), "~");
|
|
1925
|
-
let fileMtime = "0";
|
|
1926
|
-
try {
|
|
1927
|
-
fileMtime = statSync3(filePath).mtimeMs.toString();
|
|
1928
|
-
} catch {
|
|
1929
|
-
continue;
|
|
1930
|
-
}
|
|
1931
|
-
const processed = getIngestState(db, "gemini", stateKey);
|
|
1932
|
-
if (processed === fileMtime)
|
|
1933
|
-
continue;
|
|
1934
|
-
let chatData;
|
|
1935
|
-
try {
|
|
1936
|
-
chatData = JSON.parse(readFileSync4(filePath, "utf-8"));
|
|
1937
|
-
} catch {
|
|
1938
|
-
continue;
|
|
1939
|
-
}
|
|
1940
|
-
const sessionId = chatData.sessionId ?? chatData.id ?? basename3(filePath, ".json");
|
|
1941
|
-
if (!sessionId)
|
|
1942
|
-
continue;
|
|
1943
|
-
const startTime = chatData.startTime ?? new Date().toISOString();
|
|
1944
|
-
const projectPath = projectRoot(projectDir, chatData);
|
|
1945
|
-
const projectName = projectPath ? basename3(projectPath) : "";
|
|
1946
|
-
const existing = db.prepare(`SELECT id FROM sessions WHERE id = ?`).get(sessionId);
|
|
1947
|
-
if (!existing) {
|
|
1948
|
-
const session = {
|
|
1949
|
-
id: sessionId,
|
|
1950
|
-
agent: "gemini",
|
|
1951
|
-
project_path: projectPath,
|
|
1952
|
-
project_name: projectName,
|
|
1953
|
-
started_at: startTime,
|
|
1954
|
-
ended_at: chatData.lastUpdated ?? null,
|
|
1955
|
-
total_cost_usd: 0,
|
|
1956
|
-
total_tokens: 0,
|
|
1957
|
-
request_count: 0,
|
|
1958
|
-
machine_id: machineId
|
|
1959
|
-
};
|
|
1960
|
-
upsertSession(db, session);
|
|
1961
|
-
totalSessions++;
|
|
1962
|
-
}
|
|
1963
|
-
touchedSessions.add(sessionId);
|
|
1964
|
-
for (const [index, message] of (chatData.messages ?? []).entries()) {
|
|
1965
|
-
const usage = message.usage ?? message.usageMetadata ?? message.response?.usageMetadata;
|
|
1966
|
-
if (!usage)
|
|
1967
|
-
continue;
|
|
1968
|
-
const model = message.model ?? message.response?.modelVersion ?? message.response?.model ?? chatData.model;
|
|
1969
|
-
if (!model)
|
|
1970
|
-
continue;
|
|
1971
|
-
const toolUsePromptTokens = numberField(usage.toolUsePromptTokenCount, usage.tool_use_prompt_token_count);
|
|
1972
|
-
const inputTotal = numberField(usage.inputTokens, usage.input_tokens, usage.promptTokenCount, usage.prompt_token_count) + toolUsePromptTokens;
|
|
1973
|
-
const cacheReadTokens = numberField(usage.cachedInputTokens, usage.cache_read_tokens, usage.cachedContentTokenCount, usage.cached_content_token_count);
|
|
1974
|
-
const inputTokens = Math.max(inputTotal - cacheReadTokens, 0);
|
|
1975
|
-
const thoughtsTokens = numberField(usage.thoughtsTokenCount, usage.thoughts_token_count);
|
|
1976
|
-
const outputTokens = numberField(usage.outputTokens, usage.output_tokens, usage.candidatesTokenCount, usage.candidates_token_count) + thoughtsTokens;
|
|
1977
|
-
const totalTokens = numberField(usage.totalTokens, usage.total_tokens, usage.totalTokenCount, usage.total_token_count);
|
|
1978
|
-
if (inputTokens + outputTokens + cacheReadTokens + totalTokens === 0)
|
|
1979
|
-
continue;
|
|
1980
|
-
const computedCost = computeCostFromDb(db, model, inputTokens, outputTokens, cacheReadTokens, 0);
|
|
1981
|
-
const costUsd = numberField(message.costUsd, message.cost_usd) || computedCost;
|
|
1982
|
-
const timestamp = message.timestamp ?? chatData.lastUpdated ?? startTime;
|
|
1983
|
-
const requestId = `gemini-${sessionId}-${message.id ?? index}`;
|
|
1984
|
-
upsertRequest(db, {
|
|
1985
|
-
id: requestId,
|
|
1986
|
-
agent: "gemini",
|
|
1987
|
-
session_id: sessionId,
|
|
1988
|
-
model,
|
|
1989
|
-
input_tokens: inputTokens,
|
|
1990
|
-
output_tokens: outputTokens,
|
|
1991
|
-
cache_read_tokens: cacheReadTokens,
|
|
1992
|
-
cache_create_tokens: 0,
|
|
1993
|
-
cost_usd: costUsd,
|
|
1994
|
-
cost_basis: defaultCostBasisForAgent("gemini"),
|
|
1995
|
-
duration_ms: 0,
|
|
1996
|
-
timestamp,
|
|
1997
|
-
source_request_id: message.id ?? requestId,
|
|
1998
|
-
machine_id: machineId
|
|
1999
|
-
});
|
|
2000
|
-
totalRequests++;
|
|
2001
|
-
}
|
|
2002
|
-
setIngestState(db, "gemini", stateKey, fileMtime);
|
|
2003
|
-
}
|
|
2004
|
-
}
|
|
2005
|
-
for (const sessionId of touchedSessions) {
|
|
2006
|
-
rollupSession(db, sessionId);
|
|
2007
|
-
}
|
|
2008
|
-
return { sessions: totalSessions, requests: totalRequests };
|
|
960
|
+
return { sessions: ingested };
|
|
2009
961
|
}
|
|
2010
962
|
export {
|
|
2011
|
-
upsertUsageSnapshot,
|
|
2012
|
-
upsertSubscription,
|
|
2013
963
|
upsertSession,
|
|
2014
964
|
upsertRequest,
|
|
2015
965
|
upsertProject,
|
|
2016
966
|
upsertModelPricing,
|
|
2017
967
|
upsertGoal,
|
|
2018
968
|
upsertBudget,
|
|
2019
|
-
upsertBillingDaily,
|
|
2020
|
-
syncOpenProjectsRegistry,
|
|
2021
969
|
setIngestState,
|
|
2022
970
|
setActiveModel,
|
|
2023
971
|
seedModelPricing,
|
|
2024
972
|
rollupSession,
|
|
2025
973
|
readCodexModel,
|
|
2026
|
-
queryUsageSnapshots,
|
|
2027
974
|
queryTopSessions,
|
|
2028
975
|
querySummary,
|
|
2029
976
|
querySessions,
|
|
@@ -2031,27 +978,18 @@ export {
|
|
|
2031
978
|
queryProjectBreakdown,
|
|
2032
979
|
queryModelBreakdown,
|
|
2033
980
|
queryDailyBreakdown,
|
|
2034
|
-
queryBillingSummary,
|
|
2035
981
|
openDatabase,
|
|
2036
982
|
normalizeModelName,
|
|
2037
|
-
listSubscriptions,
|
|
2038
983
|
listProjects,
|
|
2039
984
|
listModelPricing,
|
|
2040
|
-
listMachines,
|
|
2041
|
-
listMachineRegistry,
|
|
2042
985
|
listGoals,
|
|
2043
986
|
listBudgets,
|
|
2044
|
-
isAgent,
|
|
2045
|
-
ingestTakumi,
|
|
2046
|
-
ingestJsonlProjects,
|
|
2047
|
-
ingestGemini,
|
|
2048
987
|
ingestCodex,
|
|
2049
988
|
ingestClaude,
|
|
2050
989
|
getProject,
|
|
2051
990
|
getPricingFromDb,
|
|
2052
991
|
getPricing,
|
|
2053
992
|
getModelPricing,
|
|
2054
|
-
getMachineId,
|
|
2055
993
|
getIngestState,
|
|
2056
994
|
getGoalStatuses,
|
|
2057
995
|
getDbPath,
|
|
@@ -2060,18 +998,13 @@ export {
|
|
|
2060
998
|
getActiveModel,
|
|
2061
999
|
gatherTrainingData,
|
|
2062
1000
|
ensurePricingSeeded,
|
|
2063
|
-
deleteSubscription,
|
|
2064
1001
|
deleteProject,
|
|
2065
1002
|
deleteModelPricing,
|
|
2066
1003
|
deleteGoal,
|
|
2067
1004
|
deleteBudget,
|
|
2068
|
-
dedupeRequests,
|
|
2069
1005
|
computeCostFromDb,
|
|
2070
1006
|
computeCost,
|
|
2071
|
-
clearBillingRange,
|
|
2072
1007
|
clearActiveModel,
|
|
2073
1008
|
DEFAULT_PRICING,
|
|
2074
|
-
DEFAULT_MODEL
|
|
2075
|
-
COST_BASIS,
|
|
2076
|
-
AGENTS
|
|
1009
|
+
DEFAULT_MODEL
|
|
2077
1010
|
};
|