@@ -6,8 +6,10 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test"
66import { existsSync , mkdirSync , rmSync } from "node:fs"
77import { join } from "node:path"
88import {
9+ formatCost ,
910 formatDuration ,
1011 formatMetricsSummary ,
12+ formatTokenCount ,
1113 getAverageCycleDuration ,
1214 getTaskSuccessRate ,
1315 loadMetrics ,
@@ -18,6 +20,7 @@ import {
1820 recordTaskCompleted ,
1921 recordTaskFailed ,
2022 recordTaskSkipped ,
23+ recordTokenUsage ,
2124 resetMetrics ,
2225 saveMetrics ,
2326} from "../src/metrics.ts"
@@ -37,6 +40,9 @@ function createTestMetrics(overrides?: Partial<Metrics>): Metrics {
3740 ideasProcessed : 0 ,
3841 totalCycleDurationMs : 0 ,
3942 lastActivityTime : new Date ( ) . toISOString ( ) ,
43+ totalInputTokens : 0 ,
44+ totalOutputTokens : 0 ,
45+ totalCostUsd : 0 ,
4046 ...overrides ,
4147 }
4248}
@@ -326,8 +332,71 @@ describe("metrics", () => {
326332
327333 expect ( metrics . cyclesCompleted ) . toBe ( 0 )
328334 expect ( metrics . tasksCompleted ) . toBe ( 0 )
335+ expect ( metrics . totalInputTokens ) . toBe ( 0 )
336+ expect ( metrics . totalOutputTokens ) . toBe ( 0 )
337+ expect ( metrics . totalCostUsd ) . toBe ( 0 )
329338 expect ( metrics . firstRunTime ) . toBeTruthy ( )
330339 expect ( metrics . lastActivityTime ) . toBeTruthy ( )
331340 } )
332341 } )
342+
343+ describe ( "recordTokenUsage" , ( ) => {
344+ test ( "accumulates token counts and cost" , ( ) => {
345+ let metrics = createTestMetrics ( )
346+
347+ metrics = recordTokenUsage ( metrics , 1000 , 500 , 0.05 )
348+ expect ( metrics . totalInputTokens ) . toBe ( 1000 )
349+ expect ( metrics . totalOutputTokens ) . toBe ( 500 )
350+ expect ( metrics . totalCostUsd ) . toBeCloseTo ( 0.05 , 4 )
351+
352+ metrics = recordTokenUsage ( metrics , 2000 , 1000 , 0.1 )
353+ expect ( metrics . totalInputTokens ) . toBe ( 3000 )
354+ expect ( metrics . totalOutputTokens ) . toBe ( 1500 )
355+ expect ( metrics . totalCostUsd ) . toBeCloseTo ( 0.15 , 4 )
356+ } )
357+
358+ test ( "handles zero values" , ( ) => {
359+ let metrics = createTestMetrics ( )
360+
361+ metrics = recordTokenUsage ( metrics , 0 , 0 , 0 )
362+ expect ( metrics . totalInputTokens ) . toBe ( 0 )
363+ expect ( metrics . totalOutputTokens ) . toBe ( 0 )
364+ expect ( metrics . totalCostUsd ) . toBe ( 0 )
365+ } )
366+ } )
367+
368+ describe ( "formatTokenCount" , ( ) => {
369+ test ( "formats small numbers" , ( ) => {
370+ expect ( formatTokenCount ( 0 ) ) . toBe ( "0" )
371+ expect ( formatTokenCount ( 123 ) ) . toBe ( "123" )
372+ expect ( formatTokenCount ( 999 ) ) . toBe ( "999" )
373+ } )
374+
375+ test ( "formats thousands as K" , ( ) => {
376+ expect ( formatTokenCount ( 1000 ) ) . toBe ( "1.0K" )
377+ expect ( formatTokenCount ( 1500 ) ) . toBe ( "1.5K" )
378+ expect ( formatTokenCount ( 50000 ) ) . toBe ( "50.0K" )
379+ expect ( formatTokenCount ( 999999 ) ) . toBe ( "1000.0K" )
380+ } )
381+
382+ test ( "formats millions as M" , ( ) => {
383+ expect ( formatTokenCount ( 1000000 ) ) . toBe ( "1.0M" )
384+ expect ( formatTokenCount ( 1500000 ) ) . toBe ( "1.5M" )
385+ expect ( formatTokenCount ( 10000000 ) ) . toBe ( "10.0M" )
386+ } )
387+ } )
388+
389+ describe ( "formatCost" , ( ) => {
390+ test ( "formats small costs with 4 decimals" , ( ) => {
391+ expect ( formatCost ( 0.001 ) ) . toBe ( "$0.0010" )
392+ expect ( formatCost ( 0.0099 ) ) . toBe ( "$0.0099" )
393+ } )
394+
395+ test ( "formats normal costs with 2 decimals" , ( ) => {
396+ expect ( formatCost ( 0.01 ) ) . toBe ( "$0.01" )
397+ expect ( formatCost ( 0.1 ) ) . toBe ( "$0.10" )
398+ expect ( formatCost ( 1.0 ) ) . toBe ( "$1.00" )
399+ expect ( formatCost ( 10.5 ) ) . toBe ( "$10.50" )
400+ } )
401+ } )
333402} )
0 commit comments