refactor(cli/stats): Stage 4 — fully Effect-native body (#25523)

This commit is contained in:
Kit Langton
2026-05-02 23:08:26 -04:00
committed by GitHub
parent a79a6594b0
commit bdabb102fe
+21 -43
View File
@@ -5,7 +5,6 @@ import { Database } from "@/storage/db"
import { SessionTable } from "../../session/session.sql"
import { Project } from "@/project/project"
import { InstanceRef } from "@/effect/instance-ref"
import { AppRuntime } from "@/effect/app-runtime"
interface SessionStats {
totalSessions: number
@@ -69,38 +68,28 @@ export const StatsCommand = effectCmd({
handler: Effect.fn("Cli.stats")(function* (args) {
const ctx = yield* InstanceRef
if (!ctx) return
return yield* run(args, ctx.project)
}),
})
const run = (
args: { days?: number; tools?: number; models?: unknown; project?: string },
currentProject: Project.Info,
) =>
Effect.promise(async () => {
const stats = await aggregateSessionStats(args.days, args.project, currentProject)
const stats = yield* aggregateSessionStats(args.days, args.project, ctx.project)
let modelLimit: number | undefined
if (args.models === true) {
modelLimit = Infinity
} else if (typeof args.models === "number") {
modelLimit = args.models
}
displayStats(stats, args.tools, modelLimit)
})
}),
})
async function getAllSessions(): Promise<Session.Info[]> {
const rows = Database.use((db) => db.select().from(SessionTable).all())
return rows.map((row) => Session.fromRow(row))
}
const getAllSessions = Effect.sync(() =>
Database.use((db) => db.select().from(SessionTable).all()).map((row) => Session.fromRow(row)),
)
export async function aggregateSessionStats(
const aggregateSessionStats = Effect.fn("Cli.stats.aggregate")(function* (
days?: number,
projectFilter?: string,
currentProject?: Project.Info,
): Promise<SessionStats> {
const sessions = await getAllSessions()
) {
const svc = yield* Session.Service
const sessions = yield* getAllSessions
const MS_IN_DAY = 24 * 60 * 60 * 1000
const cutoffTime = (() => {
@@ -169,14 +158,11 @@ export async function aggregateSessionStats(
const sessionTotalTokens: number[] = []
const BATCH_SIZE = 20
for (let i = 0; i < filteredSessions.length; i += BATCH_SIZE) {
const batch = filteredSessions.slice(i, i + BATCH_SIZE)
const batchPromises = batch.map(async (session) => {
const messages = await AppRuntime.runPromise(
Session.Service.use((svc) => svc.messages({ sessionID: session.id })),
)
const results = yield* Effect.forEach(
filteredSessions,
(session) =>
Effect.gen(function* () {
const messages = yield* svc.messages({ sessionID: session.id })
let sessionCost = 0
let sessionTokens = { input: 0, output: 0, reasoning: 0, cache: { read: 0, write: 0 } }
@@ -185,14 +171,7 @@ export async function aggregateSessionStats(
string,
{
messages: number
tokens: {
input: number
output: number
cache: {
read: number
write: number
}
}
tokens: { input: number; output: number; cache: { read: number; write: number } }
cost: number
}
> = {}
@@ -249,11 +228,11 @@ export async function aggregateSessionStats(
earliestTime: cutoffTime > 0 ? session.time.updated : session.time.created,
latestTime: session.time.updated,
}
})
}),
{ concurrency: 20 },
)
const batchResults = await Promise.all(batchPromises)
for (const result of batchResults) {
for (const result of results) {
earliestTime = Math.min(earliestTime, result.earliestTime)
latestTime = Math.max(latestTime, result.latestTime)
sessionTotalTokens.push(result.sessionTotalTokens)
@@ -286,7 +265,6 @@ export async function aggregateSessionStats(
stats.modelUsage[model].cost += usage.cost
}
}
}
const rangeDays = Math.max(1, Math.ceil((latestTime - earliestTime) / MS_IN_DAY))
const effectiveDays = windowDays ?? rangeDays
@@ -313,7 +291,7 @@ export async function aggregateSessionStats(
: sessionTotalTokens[mid]
return stats
}
})
export function displayStats(stats: SessionStats, toolLimit?: number, modelLimit?: number) {
const width = 56