(filterDays?: number)
| 125 | * Build full cost report from JSONL data |
| 126 | */ |
| 127 | export async function buildCostReport(filterDays?: number) { |
| 128 | const allEntries = await scanAllUsage(); |
| 129 | |
| 130 | const now = new Date(); |
| 131 | const todayStr = now.toISOString().split("T")[0]; |
| 132 | const yesterday = new Date(now); |
| 133 | yesterday.setDate(yesterday.getDate() - 1); |
| 134 | const yesterdayStr = yesterday.toISOString().split("T")[0]; |
| 135 | const thisMonthPrefix = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`; |
| 136 | const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1); |
| 137 | const lastMonthPrefix = `${lastMonth.getFullYear()}-${String(lastMonth.getMonth() + 1).padStart(2, "0")}`; |
| 138 | |
| 139 | // Filter by timeframe if specified |
| 140 | let cutoffDate = ""; |
| 141 | if (filterDays) { |
| 142 | const cutoff = new Date(now); |
| 143 | cutoff.setDate(cutoff.getDate() - filterDays); |
| 144 | cutoffDate = cutoff.toISOString().split("T")[0]; |
| 145 | } |
| 146 | |
| 147 | const filtered = cutoffDate |
| 148 | ? allEntries.filter((e) => e.date >= cutoffDate) |
| 149 | : allEntries; |
| 150 | |
| 151 | // Calculate costs for each entry |
| 152 | function entryCost(e: UsageEntry): number { |
| 153 | return calculateCost(e.model, e.inputTokens, e.outputTokens, e.cacheReadTokens, e.cacheWriteTokens); |
| 154 | } |
| 155 | |
| 156 | // Summary costs |
| 157 | const todayEntries = allEntries.filter((e) => e.date === todayStr); |
| 158 | const yesterdayEntries = allEntries.filter((e) => e.date === yesterdayStr); |
| 159 | const thisMonthEntries = allEntries.filter((e) => e.date.startsWith(thisMonthPrefix)); |
| 160 | const lastMonthEntries = allEntries.filter((e) => e.date.startsWith(lastMonthPrefix)); |
| 161 | |
| 162 | const todayCost = todayEntries.reduce((s, e) => s + entryCost(e), 0); |
| 163 | const yesterdayCost = yesterdayEntries.reduce((s, e) => s + entryCost(e), 0); |
| 164 | const thisMonthCost = thisMonthEntries.reduce((s, e) => s + entryCost(e), 0); |
| 165 | const lastMonthCost = lastMonthEntries.reduce((s, e) => s + entryCost(e), 0); |
| 166 | |
| 167 | const daysInMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate(); |
| 168 | const daysElapsed = now.getDate(); |
| 169 | const projected = daysElapsed > 0 ? (thisMonthCost / daysElapsed) * daysInMonth : 0; |
| 170 | |
| 171 | // By agent |
| 172 | const agentMap = new Map<string, { cost: number; tokens: number; in: number; out: number; cr: number; cw: number; msgs: number }>(); |
| 173 | for (const e of filtered) { |
| 174 | const key = e.agentId; |
| 175 | const prev = agentMap.get(key) || { cost: 0, tokens: 0, in: 0, out: 0, cr: 0, cw: 0, msgs: 0 }; |
| 176 | prev.cost += entryCost(e); |
| 177 | prev.tokens += e.totalTokens; |
| 178 | prev.in += e.inputTokens; |
| 179 | prev.out += e.outputTokens; |
| 180 | prev.cr += e.cacheReadTokens; |
| 181 | prev.cw += e.cacheWriteTokens; |
| 182 | prev.msgs += 1; |
| 183 | agentMap.set(key, prev); |
| 184 | } |
no test coverage detected