@@ -5,7 +5,6 @@ import { Database } from "@/storage/db"
55import { SessionTable } from "../../session/session.sql"
66import { Project } from "@/project/project"
77import { InstanceRef } from "@/effect/instance-ref"
8- import { AppRuntime } from "@/effect/app-runtime"
98
109interface SessionStats {
1110 totalSessions : number
@@ -69,38 +68,28 @@ export const StatsCommand = effectCmd({
6968 handler : Effect . fn ( "Cli.stats" ) ( function * ( args ) {
7069 const ctx = yield * InstanceRef
7170 if ( ! ctx ) return
72- return yield * run ( args , ctx . project )
73- } ) ,
74- } )
75-
76- const run = (
77- args : { days ?: number ; tools ?: number ; models ?: unknown ; project ?: string } ,
78- currentProject : Project . Info ,
79- ) =>
80- Effect . promise ( async ( ) => {
81- const stats = await aggregateSessionStats ( args . days , args . project , currentProject )
82-
71+ const stats = yield * aggregateSessionStats ( args . days , args . project , ctx . project )
8372 let modelLimit : number | undefined
8473 if ( args . models === true ) {
8574 modelLimit = Infinity
8675 } else if ( typeof args . models === "number" ) {
8776 modelLimit = args . models
8877 }
89-
9078 displayStats ( stats , args . tools , modelLimit )
91- } )
79+ } ) ,
80+ } )
9281
93- async function getAllSessions ( ) : Promise < Session . Info [ ] > {
94- const rows = Database . use ( ( db ) => db . select ( ) . from ( SessionTable ) . all ( ) )
95- return rows . map ( ( row ) => Session . fromRow ( row ) )
96- }
82+ const getAllSessions = Effect . sync ( ( ) =>
83+ Database . use ( ( db ) => db . select ( ) . from ( SessionTable ) . all ( ) ) . map ( ( row ) => Session . fromRow ( row ) ) ,
84+ )
9785
98- export async function aggregateSessionStats (
86+ const aggregateSessionStats = Effect . fn ( "Cli.stats.aggregate" ) ( function * (
9987 days ?: number ,
10088 projectFilter ?: string ,
10189 currentProject ?: Project . Info ,
102- ) : Promise < SessionStats > {
103- const sessions = await getAllSessions ( )
90+ ) {
91+ const svc = yield * Session . Service
92+ const sessions = yield * getAllSessions
10493 const MS_IN_DAY = 24 * 60 * 60 * 1000
10594
10695 const cutoffTime = ( ( ) => {
@@ -169,122 +158,111 @@ export async function aggregateSessionStats(
169158
170159 const sessionTotalTokens : number [ ] = [ ]
171160
172- const BATCH_SIZE = 20
173- for ( let i = 0 ; i < filteredSessions . length ; i += BATCH_SIZE ) {
174- const batch = filteredSessions . slice ( i , i + BATCH_SIZE )
175-
176- const batchPromises = batch . map ( async ( session ) => {
177- const messages = await AppRuntime . runPromise (
178- Session . Service . use ( ( svc ) => svc . messages ( { sessionID : session . id } ) ) ,
179- )
180-
181- let sessionCost = 0
182- let sessionTokens = { input : 0 , output : 0 , reasoning : 0 , cache : { read : 0 , write : 0 } }
183- let sessionToolUsage : Record < string , number > = { }
184- let sessionModelUsage : Record <
185- string ,
186- {
187- messages : number
188- tokens : {
189- input : number
190- output : number
191- cache : {
192- read : number
193- write : number
194- }
161+ const results = yield * Effect . forEach (
162+ filteredSessions ,
163+ ( session ) =>
164+ Effect . gen ( function * ( ) {
165+ const messages = yield * svc . messages ( { sessionID : session . id } )
166+
167+ let sessionCost = 0
168+ let sessionTokens = { input : 0 , output : 0 , reasoning : 0 , cache : { read : 0 , write : 0 } }
169+ let sessionToolUsage : Record < string , number > = { }
170+ let sessionModelUsage : Record <
171+ string ,
172+ {
173+ messages : number
174+ tokens : { input : number ; output : number ; cache : { read : number ; write : number } }
175+ cost : number
195176 }
196- cost : number
197- }
198- > = { }
199-
200- for ( const message of messages ) {
201- if ( message . info . role === "assistant" ) {
202- sessionCost += message . info . cost || 0
203-
204- const modelKey = `${ message . info . providerID } /${ message . info . modelID } `
205- if ( ! sessionModelUsage [ modelKey ] ) {
206- sessionModelUsage [ modelKey ] = {
207- messages : 0 ,
208- tokens : { input : 0 , output : 0 , cache : { read : 0 , write : 0 } } ,
209- cost : 0 ,
177+ > = { }
178+
179+ for ( const message of messages ) {
180+ if ( message . info . role === "assistant" ) {
181+ sessionCost += message . info . cost || 0
182+
183+ const modelKey = `${ message . info . providerID } /${ message . info . modelID } `
184+ if ( ! sessionModelUsage [ modelKey ] ) {
185+ sessionModelUsage [ modelKey ] = {
186+ messages : 0 ,
187+ tokens : { input : 0 , output : 0 , cache : { read : 0 , write : 0 } } ,
188+ cost : 0 ,
189+ }
190+ }
191+ sessionModelUsage [ modelKey ] . messages ++
192+ sessionModelUsage [ modelKey ] . cost += message . info . cost || 0
193+
194+ if ( message . info . tokens ) {
195+ sessionTokens . input += message . info . tokens . input || 0
196+ sessionTokens . output += message . info . tokens . output || 0
197+ sessionTokens . reasoning += message . info . tokens . reasoning || 0
198+ sessionTokens . cache . read += message . info . tokens . cache ?. read || 0
199+ sessionTokens . cache . write += message . info . tokens . cache ?. write || 0
200+
201+ sessionModelUsage [ modelKey ] . tokens . input += message . info . tokens . input || 0
202+ sessionModelUsage [ modelKey ] . tokens . output +=
203+ ( message . info . tokens . output || 0 ) + ( message . info . tokens . reasoning || 0 )
204+ sessionModelUsage [ modelKey ] . tokens . cache . read += message . info . tokens . cache ?. read || 0
205+ sessionModelUsage [ modelKey ] . tokens . cache . write += message . info . tokens . cache ?. write || 0
210206 }
211207 }
212- sessionModelUsage [ modelKey ] . messages ++
213- sessionModelUsage [ modelKey ] . cost += message . info . cost || 0
214-
215- if ( message . info . tokens ) {
216- sessionTokens . input += message . info . tokens . input || 0
217- sessionTokens . output += message . info . tokens . output || 0
218- sessionTokens . reasoning += message . info . tokens . reasoning || 0
219- sessionTokens . cache . read += message . info . tokens . cache ?. read || 0
220- sessionTokens . cache . write += message . info . tokens . cache ?. write || 0
221-
222- sessionModelUsage [ modelKey ] . tokens . input += message . info . tokens . input || 0
223- sessionModelUsage [ modelKey ] . tokens . output +=
224- ( message . info . tokens . output || 0 ) + ( message . info . tokens . reasoning || 0 )
225- sessionModelUsage [ modelKey ] . tokens . cache . read += message . info . tokens . cache ?. read || 0
226- sessionModelUsage [ modelKey ] . tokens . cache . write += message . info . tokens . cache ?. write || 0
227- }
228- }
229208
230- for ( const part of message . parts ) {
231- if ( part . type === "tool" && part . tool ) {
232- sessionToolUsage [ part . tool ] = ( sessionToolUsage [ part . tool ] || 0 ) + 1
209+ for ( const part of message . parts ) {
210+ if ( part . type === "tool" && part . tool ) {
211+ sessionToolUsage [ part . tool ] = ( sessionToolUsage [ part . tool ] || 0 ) + 1
212+ }
233213 }
234214 }
235- }
236-
237- return {
238- messageCount : messages . length ,
239- sessionCost,
240- sessionTokens,
241- sessionTotalTokens :
242- sessionTokens . input +
243- sessionTokens . output +
244- sessionTokens . reasoning +
245- sessionTokens . cache . read +
246- sessionTokens . cache . write ,
247- sessionToolUsage,
248- sessionModelUsage,
249- earliestTime : cutoffTime > 0 ? session . time . updated : session . time . created ,
250- latestTime : session . time . updated ,
251- }
252- } )
253-
254- const batchResults = await Promise . all ( batchPromises )
255215
256- for ( const result of batchResults ) {
257- earliestTime = Math . min ( earliestTime , result . earliestTime )
258- latestTime = Math . max ( latestTime , result . latestTime )
259- sessionTotalTokens . push ( result . sessionTotalTokens )
260-
261- stats . totalMessages += result . messageCount
262- stats . totalCost += result . sessionCost
263- stats . totalTokens . input += result . sessionTokens . input
264- stats . totalTokens . output += result . sessionTokens . output
265- stats . totalTokens . reasoning += result . sessionTokens . reasoning
266- stats . totalTokens . cache . read += result . sessionTokens . cache . read
267- stats . totalTokens . cache . write += result . sessionTokens . cache . write
268-
269- for ( const [ tool , count ] of Object . entries ( result . sessionToolUsage ) ) {
270- stats . toolUsage [ tool ] = ( stats . toolUsage [ tool ] || 0 ) + count
271- }
216+ return {
217+ messageCount : messages . length ,
218+ sessionCost,
219+ sessionTokens,
220+ sessionTotalTokens :
221+ sessionTokens . input +
222+ sessionTokens . output +
223+ sessionTokens . reasoning +
224+ sessionTokens . cache . read +
225+ sessionTokens . cache . write ,
226+ sessionToolUsage,
227+ sessionModelUsage,
228+ earliestTime : cutoffTime > 0 ? session . time . updated : session . time . created ,
229+ latestTime : session . time . updated ,
230+ }
231+ } ) ,
232+ { concurrency : 20 } ,
233+ )
234+
235+ for ( const result of results ) {
236+ earliestTime = Math . min ( earliestTime , result . earliestTime )
237+ latestTime = Math . max ( latestTime , result . latestTime )
238+ sessionTotalTokens . push ( result . sessionTotalTokens )
239+
240+ stats . totalMessages += result . messageCount
241+ stats . totalCost += result . sessionCost
242+ stats . totalTokens . input += result . sessionTokens . input
243+ stats . totalTokens . output += result . sessionTokens . output
244+ stats . totalTokens . reasoning += result . sessionTokens . reasoning
245+ stats . totalTokens . cache . read += result . sessionTokens . cache . read
246+ stats . totalTokens . cache . write += result . sessionTokens . cache . write
247+
248+ for ( const [ tool , count ] of Object . entries ( result . sessionToolUsage ) ) {
249+ stats . toolUsage [ tool ] = ( stats . toolUsage [ tool ] || 0 ) + count
250+ }
272251
273- for ( const [ model , usage ] of Object . entries ( result . sessionModelUsage ) ) {
274- if ( ! stats . modelUsage [ model ] ) {
275- stats . modelUsage [ model ] = {
276- messages : 0 ,
277- tokens : { input : 0 , output : 0 , cache : { read : 0 , write : 0 } } ,
278- cost : 0 ,
279- }
252+ for ( const [ model , usage ] of Object . entries ( result . sessionModelUsage ) ) {
253+ if ( ! stats . modelUsage [ model ] ) {
254+ stats . modelUsage [ model ] = {
255+ messages : 0 ,
256+ tokens : { input : 0 , output : 0 , cache : { read : 0 , write : 0 } } ,
257+ cost : 0 ,
280258 }
281- stats . modelUsage [ model ] . messages += usage . messages
282- stats . modelUsage [ model ] . tokens . input += usage . tokens . input
283- stats . modelUsage [ model ] . tokens . output += usage . tokens . output
284- stats . modelUsage [ model ] . tokens . cache . read += usage . tokens . cache . read
285- stats . modelUsage [ model ] . tokens . cache . write += usage . tokens . cache . write
286- stats . modelUsage [ model ] . cost += usage . cost
287259 }
260+ stats . modelUsage [ model ] . messages += usage . messages
261+ stats . modelUsage [ model ] . tokens . input += usage . tokens . input
262+ stats . modelUsage [ model ] . tokens . output += usage . tokens . output
263+ stats . modelUsage [ model ] . tokens . cache . read += usage . tokens . cache . read
264+ stats . modelUsage [ model ] . tokens . cache . write += usage . tokens . cache . write
265+ stats . modelUsage [ model ] . cost += usage . cost
288266 }
289267 }
290268
@@ -313,7 +291,7 @@ export async function aggregateSessionStats(
313291 : sessionTotalTokens [ mid ]
314292
315293 return stats
316- }
294+ } )
317295
318296export function displayStats ( stats : SessionStats , toolLimit ?: number , modelLimit ?: number ) {
319297 const width = 56
0 commit comments