@@ -40,6 +40,8 @@ function buildSessionRollup(rollupKey: string, events: CanonicalEvent[]): Sessio
4040 const lastEventAt = ordered . at ( - 1 ) ?. ts || startedAt
4141 const timeBuckets = new Map < string , SessionRollup [ 'timeBuckets' ] [ number ] > ( )
4242 const modelRollups = new Map < string , SessionRollup [ 'modelRollups' ] [ number ] > ( )
43+ // Keyed by `${bucketTs}\0${model}` (v3 per-(15-min bucket, model) token buckets).
44+ const modelBuckets = new Map < string , NonNullable < SessionRollup [ 'modelBuckets' ] > [ number ] > ( )
4345 const toolRollups = new Map < string , SessionRollup [ 'toolRollups' ] [ number ] > ( )
4446 const fileRollups = new Map < string , SessionRollup [ 'fileRollups' ] [ number ] > ( )
4547 const turnRollups = new Map < string , NonNullable < SessionRollup [ 'turnRollups' ] > [ number ] > ( )
@@ -65,6 +67,9 @@ function buildSessionRollup(rollupKey: string, events: CanonicalEvent[]): Sessio
6567 const eventInputTokens = Math . max ( 0 , event . metrics ?. tokensInput || 0 )
6668 const eventCachedInputTokens = Math . max ( 0 , event . metrics ?. tokensCachedInput || 0 )
6769 const eventCacheCreationInputTokens = Math . max ( 0 , event . metrics ?. tokensCacheCreationInput || 0 )
70+ // TTL split subsets of cacheCreation; 0 when the agent doesn't report them.
71+ const eventCacheCreation5mInputTokens = Math . max ( 0 , event . metrics ?. tokensCacheCreation5mInput || 0 )
72+ const eventCacheCreation1hInputTokens = Math . max ( 0 , event . metrics ?. tokensCacheCreation1hInput || 0 )
6873 const eventCacheReadInputTokens = Math . max ( 0 , event . metrics ?. tokensCacheReadInput || 0 )
6974 const eventOutputTokens = Math . max ( 0 , event . metrics ?. tokensOutput || 0 )
7075 const eventReasoningOutputTokens = Math . max ( 0 , event . metrics ?. tokensReasoningOutput || 0 )
@@ -197,6 +202,8 @@ function buildSessionRollup(rollupKey: string, events: CanonicalEvent[]): Sessio
197202 inputTokens : 0 ,
198203 cachedInputTokens : 0 ,
199204 cacheCreationInputTokens : 0 ,
205+ cacheCreation5mInputTokens : 0 ,
206+ cacheCreation1hInputTokens : 0 ,
200207 cacheReadInputTokens : 0 ,
201208 outputTokens : 0 ,
202209 reasoningOutputTokens : 0 ,
@@ -207,12 +214,42 @@ function buildSessionRollup(rollupKey: string, events: CanonicalEvent[]): Sessio
207214 modelRollup . inputTokens += eventInputTokens
208215 modelRollup . cachedInputTokens += eventCachedInputTokens
209216 modelRollup . cacheCreationInputTokens += eventCacheCreationInputTokens
217+ modelRollup . cacheCreation5mInputTokens ! += eventCacheCreation5mInputTokens
218+ modelRollup . cacheCreation1hInputTokens ! += eventCacheCreation1hInputTokens
210219 modelRollup . cacheReadInputTokens += eventCacheReadInputTokens
211220 modelRollup . outputTokens += eventOutputTokens
212221 modelRollup . reasoningOutputTokens += eventReasoningOutputTokens
213222 modelRollup . totalTokens += eventTotalTokens
214223 modelRollup . estimatedCostUsd += eventCostUsd
215224 modelRollups . set ( modelKey , modelRollup )
225+
226+ // Per-(15-min bucket, model) token bucket (v3).
227+ const modelBucketKey = `${ bucketTs } \0${ modelKey } `
228+ const modelBucket = modelBuckets . get ( modelBucketKey ) || {
229+ ts : bucketTs ,
230+ model : modelKey ,
231+ callCount : 0 ,
232+ inputTokens : 0 ,
233+ cachedInputTokens : 0 ,
234+ cacheCreationInputTokens : 0 ,
235+ cacheCreation5mInputTokens : 0 ,
236+ cacheCreation1hInputTokens : 0 ,
237+ cacheReadInputTokens : 0 ,
238+ outputTokens : 0 ,
239+ reasoningOutputTokens : 0 ,
240+ totalTokens : 0 ,
241+ }
242+ modelBucket . callCount += 1
243+ modelBucket . inputTokens += eventInputTokens
244+ modelBucket . cachedInputTokens += eventCachedInputTokens
245+ modelBucket . cacheCreationInputTokens += eventCacheCreationInputTokens
246+ modelBucket . cacheCreation5mInputTokens += eventCacheCreation5mInputTokens
247+ modelBucket . cacheCreation1hInputTokens += eventCacheCreation1hInputTokens
248+ modelBucket . cacheReadInputTokens += eventCacheReadInputTokens
249+ modelBucket . outputTokens += eventOutputTokens
250+ modelBucket . reasoningOutputTokens += eventReasoningOutputTokens
251+ modelBucket . totalTokens += eventTotalTokens
252+ modelBuckets . set ( modelBucketKey , modelBucket )
216253 }
217254 if ( event . type === 'tool.started' ) {
218255 bucket . toolCalls += 1
@@ -284,11 +321,12 @@ function buildSessionRollup(rollupKey: string, events: CanonicalEvent[]): Sessio
284321 const baseRollup : SessionRollup = {
285322 rollupKey,
286323 payloadHash : '' ,
287- // v2 schema: trustworthy gap-clamped turn durations + billable-output token
288- // convention. Set on baseRollup (not after) so it participates in payloadHash:
289- // every historical rollup's hash changes, and a re-backfill (uploaded with
290- // replace=true by default) cleanly refreshes all data onto the new convention.
291- // This full-refresh churn is intentional.
324+ // v3 schema: v2 (gap-clamped turn durations + billable-output token
325+ // convention) plus per-model cache-creation TTL split and modelBuckets. Set on
326+ // baseRollup (not after) so it participates in payloadHash: every historical
327+ // rollup's hash changes, and a re-backfill (uploaded with replace=true by
328+ // default) cleanly refreshes all data onto the new convention. This
329+ // full-refresh churn is intentional.
292330 schemaVersion : AGENT_ROLLUP_SCHEMA_VERSION ,
293331 source : first . source ,
294332 project,
@@ -313,6 +351,8 @@ function buildSessionRollup(rollupKey: string, events: CanonicalEvent[]): Sessio
313351 durationMs : Math . max ( 0 , Date . parse ( lastEventAt ) - Date . parse ( startedAt ) ) ,
314352 timeBuckets : [ ...timeBuckets . values ( ) ] . sort ( ( a , b ) => a . ts . localeCompare ( b . ts ) ) ,
315353 modelRollups : [ ...modelRollups . values ( ) ] . sort ( ( a , b ) => b . callCount - a . callCount || a . model . localeCompare ( b . model ) ) ,
354+ // Sorted ts ascending, then model lexicographically (wire contract).
355+ modelBuckets : [ ...modelBuckets . values ( ) ] . sort ( ( a , b ) => a . ts . localeCompare ( b . ts ) || a . model . localeCompare ( b . model ) ) ,
316356 toolRollups : [ ...toolRollups . values ( ) ] . sort ( ( a , b ) => b . callCount - a . callCount || a . tool . localeCompare ( b . tool ) ) ,
317357 fileRollups : [ ...fileRollups . values ( ) ] . sort ( ( a , b ) => b . writes - a . writes || b . reads - a . reads || a . displayPath . localeCompare ( b . displayPath ) ) ,
318358 turnRollups : [ ...turnRollups . values ( ) ]
0 commit comments