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

View File

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