@@ -76,6 +76,14 @@ const outputs = new Map<string, number>();
7676const cacheReads = new Map < string , number > ( ) ;
7777const cacheWrites = new Map < string , number > ( ) ;
7878const costs = new Map < string , number > ( ) ;
79+ const normalTotals = new Map < string , number > ( ) ;
80+ const normalCacheReads = new Map < string , number > ( ) ;
81+ const normalCacheWrites = new Map < string , number > ( ) ;
82+ const autoresearchTotals = new Map < string , number > ( ) ;
83+ const autoresearchCacheReads = new Map < string , number > ( ) ;
84+ const autoresearchCacheWrites = new Map < string , number > ( ) ;
85+
86+ type UsageBucket = "normal" | "autoresearch" ;
7987
8088const pad = ( n : number ) => n . toString ( ) . padStart ( 2 , "0" ) ;
8189const formatKey = ( d : Date ) => `${ d . getFullYear ( ) } -${ pad ( d . getMonth ( ) + 1 ) } -${ pad ( d . getDate ( ) ) } ` ;
@@ -166,18 +174,31 @@ for (let i = 0; i < targetDays; i += 1) {
166174 cacheReads . set ( key , 0 ) ;
167175 cacheWrites . set ( key , 0 ) ;
168176 costs . set ( key , 0 ) ;
177+ normalTotals . set ( key , 0 ) ;
178+ normalCacheReads . set ( key , 0 ) ;
179+ normalCacheWrites . set ( key , 0 ) ;
180+ autoresearchTotals . set ( key , 0 ) ;
181+ autoresearchCacheReads . set ( key , 0 ) ;
182+ autoresearchCacheWrites . set ( key , 0 ) ;
169183}
170184
171185const inRange = ( d : Date ) => d >= start && d <= now ;
172186
187+ function classifyUsageBucket ( sourceValue : unknown ) : UsageBucket {
188+ return typeof sourceValue === "string" && sourceValue . trim ( ) === "autoresearch"
189+ ? "autoresearch"
190+ : "normal" ;
191+ }
192+
173193function addTokens (
174194 ts : Date ,
175195 input : number ,
176196 output : number ,
177197 cacheRead : number ,
178198 cacheWrite : number ,
179199 totalTokens : number ,
180- costTotal : number
200+ costTotal : number ,
201+ bucket : UsageBucket = "normal"
181202) {
182203 if ( ! ts || Number . isNaN ( ts . getTime ( ) ) || ! inRange ( ts ) ) return ;
183204 const key = formatKey ( ts ) ;
@@ -189,6 +210,13 @@ function addTokens(
189210 cacheReads . set ( key , ( cacheReads . get ( key ) || 0 ) + cacheRead ) ;
190211 cacheWrites . set ( key , ( cacheWrites . get ( key ) || 0 ) + cacheWrite ) ;
191212 costs . set ( key , ( costs . get ( key ) || 0 ) + costTotal ) ;
213+
214+ const bucketTotals = bucket === "autoresearch" ? autoresearchTotals : normalTotals ;
215+ const bucketCacheReads = bucket === "autoresearch" ? autoresearchCacheReads : normalCacheReads ;
216+ const bucketCacheWrites = bucket === "autoresearch" ? autoresearchCacheWrites : normalCacheWrites ;
217+ bucketTotals . set ( key , ( bucketTotals . get ( key ) || 0 ) + totalTokens ) ;
218+ bucketCacheReads . set ( key , ( bucketCacheReads . get ( key ) || 0 ) + cacheRead ) ;
219+ bucketCacheWrites . set ( key , ( bucketCacheWrites . get ( key ) || 0 ) + cacheWrite ) ;
192220}
193221
194222function addUsage ( record : any ) {
@@ -225,7 +253,12 @@ function addUsage(record: any) {
225253 ( typeof costObj . cacheRead === "number" ? costObj . cacheRead : 0 ) +
226254 ( typeof costObj . cacheWrite === "number" ? costObj . cacheWrite : 0 ) ;
227255
228- addTokens ( ts , input , output , cacheRead , cacheWrite , totalTokens , costTotal ) ;
256+ const sourceValue =
257+ typeof record ?. source === "string"
258+ ? record . source
259+ : ( typeof msg ?. metadata ?. source === "string" ? msg . metadata . source : null ) ;
260+
261+ addTokens ( ts , input , output , cacheRead , cacheWrite , totalTokens , costTotal , classifyUsageBucket ( sourceValue ) ) ;
229262}
230263
231264function scanFile ( filePath : string ) {
@@ -274,10 +307,13 @@ function loadFromDb(): boolean {
274307 db . exec ( "PRAGMA busy_timeout = 5000;" ) ;
275308 const startIso = start . toISOString ( ) ;
276309 const endIso = now . toISOString ( ) ;
310+ const tokenUsageColumns = db . prepare ( "PRAGMA table_info(token_usage)" ) . all ( ) as Array < { name ?: string } > ;
311+ const hasSourceColumn = tokenUsageColumns . some ( ( column ) => column . name === "source" ) ;
277312 const rows = db
278313 . query (
279314 `SELECT run_at, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, total_tokens,
280- cost_input, cost_output, cost_cache_read, cost_cache_write, cost_total
315+ cost_input, cost_output, cost_cache_read, cost_cache_write, cost_total,
316+ ${ hasSourceColumn ? "source" : "NULL AS source" }
281317 FROM token_usage
282318 WHERE run_at >= ? AND run_at <= ?`
283319 )
@@ -299,7 +335,7 @@ function loadFromDb(): boolean {
299335 ( typeof row . cost_cache_read === "number" ? row . cost_cache_read : 0 ) +
300336 ( typeof row . cost_cache_write === "number" ? row . cost_cache_write : 0 ) ;
301337
302- addTokens ( ts , input , output , cacheRead , cacheWrite , totalTokens , costTotal ) ;
338+ addTokens ( ts , input , output , cacheRead , cacheWrite , totalTokens , costTotal , classifyUsageBucket ( row . source ) ) ;
303339 }
304340 return true ;
305341 } catch ( err ) {
@@ -322,6 +358,12 @@ if (useSessions || !loadedFromDb) {
322358const values = dayKeys . map ( ( key ) => totals . get ( key ) || 0 ) ;
323359const cachedValues = dayKeys . map ( ( key ) => ( cacheReads . get ( key ) || 0 ) + ( cacheWrites . get ( key ) || 0 ) ) ;
324360const uncachedValues = values . map ( ( total , idx ) => Math . max ( total - cachedValues [ idx ] , 0 ) ) ;
361+ const normalValues = dayKeys . map ( ( key ) => normalTotals . get ( key ) || 0 ) ;
362+ const normalCachedValues = dayKeys . map ( ( key ) => ( normalCacheReads . get ( key ) || 0 ) + ( normalCacheWrites . get ( key ) || 0 ) ) ;
363+ const normalUncachedValues = normalValues . map ( ( total , idx ) => Math . max ( total - normalCachedValues [ idx ] , 0 ) ) ;
364+ const autoresearchValues = dayKeys . map ( ( key ) => autoresearchTotals . get ( key ) || 0 ) ;
365+ const autoresearchCachedValues = dayKeys . map ( ( key ) => ( autoresearchCacheReads . get ( key ) || 0 ) + ( autoresearchCacheWrites . get ( key ) || 0 ) ) ;
366+ const autoresearchUncachedValues = autoresearchValues . map ( ( total , idx ) => Math . max ( total - autoresearchCachedValues [ idx ] , 0 ) ) ;
325367const rawMaxValue = Math . max ( 0 , ...values ) ;
326368const sumValue = values . reduce ( ( a , b ) => a + b , 0 ) ;
327369const sumInput = dayKeys . reduce ( ( acc , key ) => acc + ( inputs . get ( key ) || 0 ) , 0 ) ;
@@ -331,6 +373,14 @@ const sumCacheWrite = dayKeys.reduce((acc, key) => acc + (cacheWrites.get(key) |
331373const sumCost = dayKeys . reduce ( ( acc , key ) => acc + ( costs . get ( key ) || 0 ) , 0 ) ;
332374const cachedTotal = sumCacheRead + sumCacheWrite ;
333375const cachedPct = sumValue > 0 ? Math . round ( ( cachedTotal / sumValue ) * 1000 ) / 10 : 0 ;
376+ const normalTotal = normalValues . reduce ( ( acc , value ) => acc + value , 0 ) ;
377+ const normalCachedTotal = normalCachedValues . reduce ( ( acc , value ) => acc + value , 0 ) ;
378+ const normalUncachedTotal = normalUncachedValues . reduce ( ( acc , value ) => acc + value , 0 ) ;
379+ const autoresearchTotal = autoresearchValues . reduce ( ( acc , value ) => acc + value , 0 ) ;
380+ const autoresearchCachedTotal = autoresearchCachedValues . reduce ( ( acc , value ) => acc + value , 0 ) ;
381+ const autoresearchUncachedTotal = autoresearchUncachedValues . reduce ( ( acc , value ) => acc + value , 0 ) ;
382+ void uncachedValues ;
383+ void sumCost ;
334384
335385const niceNumber = ( value : number , round : boolean ) : number => {
336386 if ( value === 0 ) return 0 ;
@@ -364,36 +414,61 @@ const buildTicks = (min: number, max: number, tickCount: number) => {
364414} ;
365415
366416const width = 680 ;
367- const height = 240 ;
368- const padding = { left : 48 , right : 16 , top : 28 , bottom : 42 } ;
417+ const height = 252 ;
418+ const padding = { left : 48 , right : 16 , top : 40 , bottom : 42 } ;
369419const chartWidth = width - padding . left - padding . right ;
370420const chartHeight = height - padding . top - padding . bottom ;
371421const step = chartWidth / targetDays ;
372422const gap = Math . min ( 12 , step * 0.2 ) ;
373- const barWidth = step - gap ;
423+ const barWidth = Math . max ( 12 , step - gap ) ;
374424const { max : maxAxis , ticks : yTicks } = buildTicks ( 0 , Math . max ( 1 , rawMaxValue ) , 5 ) ;
375425const scaleY = ( value : number ) => padding . top + ( chartHeight - ( value / maxAxis ) * chartHeight ) ;
376426
377427const dayNames = [ "Sun" , "Mon" , "Tue" , "Wed" , "Thu" , "Fri" , "Sat" ] ;
378428const label = ( key : string ) => key . slice ( 5 ) ;
379429const labelLong = ( d : Date ) => `${ dayNames [ d . getDay ( ) ] } ${ pad ( d . getMonth ( ) + 1 ) } -${ pad ( d . getDate ( ) ) } ` ;
380430
381- const bars = values . map ( ( value , i ) => {
382- const x = padding . left + i * step + gap / 2 ;
383- const cached = cachedValues [ i ] || 0 ;
384- const uncached = uncachedValues [ i ] || 0 ;
385- const cachedHeight = Math . round ( ( cached / maxAxis ) * chartHeight ) ;
386- const uncachedHeight = Math . round ( ( uncached / maxAxis ) * chartHeight ) ;
387- const cachedY = padding . top + ( chartHeight - cachedHeight ) ;
388- const uncachedY = padding . top + ( chartHeight - cachedHeight - uncachedHeight ) ;
389- const rects = [ ] as string [ ] ;
390- if ( cachedHeight > 0 ) {
391- rects . push ( `<rect class="bar bar-cached" x="${ x . toFixed ( 1 ) } " y="${ cachedY . toFixed ( 1 ) } " width="${ barWidth . toFixed ( 1 ) } " height="${ cachedHeight . toFixed ( 1 ) } " rx="4" />` ) ;
392- }
393- if ( uncachedHeight > 0 ) {
394- rects . push ( `<rect class="bar bar-uncached" x="${ x . toFixed ( 1 ) } " y="${ uncachedY . toFixed ( 1 ) } " width="${ barWidth . toFixed ( 1 ) } " height="${ uncachedHeight . toFixed ( 1 ) } " rx="4" />` ) ;
431+ function buildCombinedStackRects ( x : number , dayIndex : number , dayKey : string ) : string {
432+ const segments = [
433+ {
434+ value : normalCachedValues [ dayIndex ] || 0 ,
435+ className : "bar-normal-cached" ,
436+ title : `${ dayKey } • normal cached ${ formatCompact ( normalCachedValues [ dayIndex ] || 0 ) } ` ,
437+ } ,
438+ {
439+ value : autoresearchCachedValues [ dayIndex ] || 0 ,
440+ className : "bar-autoresearch-cached" ,
441+ title : `${ dayKey } • autoresearch cached ${ formatCompact ( autoresearchCachedValues [ dayIndex ] || 0 ) } ` ,
442+ } ,
443+ {
444+ value : normalUncachedValues [ dayIndex ] || 0 ,
445+ className : "bar-normal-uncached" ,
446+ title : `${ dayKey } • normal uncached ${ formatCompact ( normalUncachedValues [ dayIndex ] || 0 ) } ` ,
447+ } ,
448+ {
449+ value : autoresearchUncachedValues [ dayIndex ] || 0 ,
450+ className : "bar-autoresearch-uncached" ,
451+ title : `${ dayKey } • autoresearch uncached ${ formatCompact ( autoresearchUncachedValues [ dayIndex ] || 0 ) } ` ,
452+ } ,
453+ ] ;
454+
455+ let cumulative = 0 ;
456+ const rects : string [ ] = [ ] ;
457+ for ( const segment of segments ) {
458+ if ( ! segment . value ) continue ;
459+ const y0 = padding . top + ( chartHeight - ( cumulative / maxAxis ) * chartHeight ) ;
460+ const next = cumulative + segment . value ;
461+ const y1 = padding . top + ( chartHeight - ( next / maxAxis ) * chartHeight ) ;
462+ const heightPx = Math . max ( 1 , y0 - y1 ) ;
463+ rects . push ( `<rect class="bar ${ segment . className } " x="${ x . toFixed ( 1 ) } " y="${ y1 . toFixed ( 1 ) } " width="${ barWidth . toFixed ( 1 ) } " height="${ heightPx . toFixed ( 1 ) } " rx="4"><title>${ segment . title } </title></rect>` ) ;
464+ cumulative = next ;
395465 }
396466 return rects . join ( "\n " ) ;
467+ }
468+
469+ const bars = dayKeys . map ( ( key , i ) => {
470+ const x = padding . left + i * step + gap / 2 ;
471+ return buildCombinedStackRects ( x , i , key ) ;
397472} ) ;
398473
399474const labels = dayKeys . map ( ( key , i ) => {
@@ -434,23 +509,29 @@ const title = `Tokens last ${targetDays} days • total ${formatCompact(sumValue
434509const svg = `<?xml version="1.0" encoding="UTF-8"?>
435510<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${ width } ${ height } " width="${ width } " height="${ height } " role="img" aria-label="${ title } ">
436511 <style>
437- svg { --text: #0f1419; --grid: #e8ebed; --tick: #cbd5e1; --bar -uncached: #1d9bf0; --bar -cached: #2ecc71; --muted: #536471; }
512+ svg { --text: #0f1419; --grid: #e8ebed; --tick: #cbd5e1; --normal -uncached: #1d9bf0; --normal -cached: #2ecc71; --autoresearch-uncached: #8b5cf6; --autoresearch-cached: #f59e0b ; --muted: #536471; }
438513 @media (prefers-color-scheme: dark) {
439- svg { --text: #e7e9ea; --grid: #2f3336; --tick: #4b5563; --bar -uncached: #1d9bf0 ; --bar -cached: #27ae60 ; --muted: #71767b; }
514+ svg { --text: #e7e9ea; --grid: #2f3336; --tick: #4b5563; --normal -uncached: #4aa8ff ; --normal -cached: #34d399; --autoresearch-uncached: #a78bfa; --autoresearch-cached: #fbbf24 ; --muted: #71767b; }
440515 }
441516 .title { font: 600 14px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; fill: var(--text); }
442517 .label { font: 11px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; fill: var(--muted); }
443518 .axis { stroke: var(--grid); stroke-width: 1.2; }
444519 .grid { stroke: var(--grid); stroke-width: 1; stroke-dasharray: 4 4; opacity: 0.8; vector-effect: non-scaling-stroke; }
445520 .tick { stroke: var(--tick); stroke-width: 1.2; vector-effect: non-scaling-stroke; }
446- .bar-uncached { fill: var(--bar-uncached); }
447- .bar-cached { fill: var(--bar-cached); }
521+ .bar-normal-uncached { fill: var(--normal-uncached); }
522+ .bar-normal-cached { fill: var(--normal-cached); }
523+ .bar-autoresearch-uncached { fill: var(--autoresearch-uncached); }
524+ .bar-autoresearch-cached { fill: var(--autoresearch-cached); }
448525 </style>
449526 <text class="title" x="${ padding . left } " y="18">${ title } </text>
450- <rect x="${ width - padding . right - 140 } " y="6" width="10" height="10" fill="var(--bar-uncached)" rx="2" />
451- <text class="label" x="${ width - padding . right - 124 } " y="15">uncached</text>
452- <rect x="${ width - padding . right - 70 } " y="6" width="10" height="10" fill="var(--bar-cached)" rx="2" />
453- <text class="label" x="${ width - padding . right - 54 } " y="15">cached</text>
527+ <rect x="${ width - padding . right - 310 } " y="6" width="10" height="10" fill="var(--normal-uncached)" rx="2" />
528+ <text class="label" x="${ width - padding . right - 294 } " y="15">normal uncached</text>
529+ <rect x="${ width - padding . right - 155 } " y="6" width="10" height="10" fill="var(--normal-cached)" rx="2" />
530+ <text class="label" x="${ width - padding . right - 139 } " y="15">normal cached</text>
531+ <rect x="${ width - padding . right - 310 } " y="20" width="10" height="10" fill="var(--autoresearch-uncached)" rx="2" />
532+ <text class="label" x="${ width - padding . right - 294 } " y="29">autoresearch uncached</text>
533+ <rect x="${ width - padding . right - 155 } " y="20" width="10" height="10" fill="var(--autoresearch-cached)" rx="2" />
534+ <text class="label" x="${ width - padding . right - 139 } " y="29">autoresearch cached</text>
454535 ${ bars . join ( "\n " ) }
455536 ${ yGrid }
456537 ${ axisX }
@@ -469,12 +550,10 @@ if (outputSvg) {
469550const summaryLines = [
470551 `Token usage (all chats) — last ${ targetDays } days, total ${ formatCompact ( sumValue ) } ` ,
471552 `Input ${ formatCompact ( sumInput ) } • Output ${ formatCompact ( sumOutput ) } • Cache read ${ formatCompact ( sumCacheRead ) } • Cache write ${ formatCompact ( sumCacheWrite ) } (${ cachedPct } % cached)` ,
553+ `Normal ${ formatCompact ( normalTotal ) } tokens • cached ${ formatCompact ( normalCachedTotal ) } • uncached ${ formatCompact ( normalUncachedTotal ) } ` ,
554+ `Autoresearch ${ formatCompact ( autoresearchTotal ) } tokens • cached ${ formatCompact ( autoresearchCachedTotal ) } • uncached ${ formatCompact ( autoresearchUncachedTotal ) } ` ,
472555 ...dayDates . map ( ( d , i ) => {
473- const key = dayKeys [ i ] ;
474- const total = values [ i ] ;
475- const cached = ( cacheReads . get ( key ) || 0 ) + ( cacheWrites . get ( key ) || 0 ) ;
476- const uncached = Math . max ( total - cached , 0 ) ;
477- return `• ${ labelLong ( d ) } : ${ formatCompact ( total ) } tokens (cached ${ formatCompact ( cached ) } , uncached ${ formatCompact ( uncached ) } )` ;
556+ return `• ${ labelLong ( d ) } : ${ formatCompact ( values [ i ] ) } tokens (normal ${ formatCompact ( normalValues [ i ] || 0 ) } : cached ${ formatCompact ( normalCachedValues [ i ] || 0 ) } , uncached ${ formatCompact ( normalUncachedValues [ i ] || 0 ) } ; autoresearch ${ formatCompact ( autoresearchValues [ i ] || 0 ) } : cached ${ formatCompact ( autoresearchCachedValues [ i ] || 0 ) } , uncached ${ formatCompact ( autoresearchUncachedValues [ i ] || 0 ) } )` ;
478557 } ) ,
479558] ;
480559
0 commit comments