@@ -39,6 +39,11 @@ export namespace SessionCompaction {
3939 const DEFAULT_TAIL_TURNS = 2
4040 const MIN_TAIL_TOKENS = 2_000
4141 const MAX_TAIL_TOKENS = 8_000
42+ type Turn = {
43+ start : number
44+ end : number
45+ id : MessageID
46+ }
4247
4348 function usable ( input : { cfg : Config . Info ; model : Provider . Model } ) {
4449 const reserved =
@@ -55,6 +60,24 @@ export namespace SessionCompaction {
5560 )
5661 }
5762
63+ function turns ( messages : MessageV2 . WithParts [ ] ) {
64+ const result : Turn [ ] = [ ]
65+ for ( let i = 0 ; i < messages . length ; i ++ ) {
66+ const msg = messages [ i ]
67+ if ( msg . info . role !== "user" ) continue
68+ if ( msg . parts . some ( ( part ) => part . type === "compaction" ) ) continue
69+ result . push ( {
70+ start : i ,
71+ end : messages . length ,
72+ id : msg . info . id ,
73+ } )
74+ }
75+ for ( let i = 0 ; i < result . length - 1 ; i ++ ) {
76+ result [ i ] . end = result [ i + 1 ] . start
77+ }
78+ return result
79+ }
80+
5881 export interface Interface {
5982 readonly isOverflow : ( input : {
6083 tokens : MessageV2 . Assistant [ "tokens" ]
@@ -123,36 +146,36 @@ export namespace SessionCompaction {
123146 const limit = input . cfg . compaction ?. tail_turns ?? DEFAULT_TAIL_TURNS
124147 if ( limit <= 0 ) return { head : input . messages , tail_start_id : undefined }
125148 const budget = tailBudget ( { cfg : input . cfg , model : input . model } )
126- const turns = input . messages . flatMap ( ( msg , idx ) =>
127- msg . info . role === "user" && ! msg . parts . some ( ( part ) => part . type === "compaction" ) ? [ idx ] : [ ] ,
149+ const all = turns ( input . messages )
150+ if ( ! all . length ) return { head : input . messages , tail_start_id : undefined }
151+ const recent = all . slice ( - limit )
152+ const sizes = yield * Effect . forEach (
153+ recent ,
154+ ( turn ) =>
155+ estimate ( {
156+ messages : input . messages . slice ( turn . start , turn . end ) ,
157+ model : input . model ,
158+ } ) ,
159+ { concurrency : 1 } ,
128160 )
129- if ( ! turns . length ) return { head : input . messages , tail_start_id : undefined }
161+ if ( sizes . at ( - 1 ) ! > budget ) {
162+ log . info ( "tail fallback" , { budget, size : sizes . at ( - 1 ) } )
163+ return { head : input . messages , tail_start_id : undefined }
164+ }
130165
131166 let total = 0
132- let start = input . messages . length
133- let kept = 0
134-
135- for ( let i = turns . length - 1 ; i >= 0 && kept < limit ; i -- ) {
136- const idx = turns [ i ]
137- const end = i + 1 < turns . length ? turns [ i + 1 ] : input . messages . length
138- const size = yield * estimate ( {
139- messages : input . messages . slice ( idx , end ) ,
140- model : input . model ,
141- } )
142- if ( kept === 0 && size > budget ) {
143- log . info ( "tail fallback" , { budget, size } )
144- return { head : input . messages , tail_start_id : undefined }
145- }
167+ let keep : Turn | undefined
168+ for ( let i = recent . length - 1 ; i >= 0 ; i -- ) {
169+ const size = sizes [ i ]
146170 if ( total + size > budget ) break
147171 total += size
148- start = idx
149- kept ++
172+ keep = recent [ i ]
150173 }
151174
152- if ( kept === 0 || start === 0 ) return { head : input . messages , tail_start_id : undefined }
175+ if ( ! keep || keep . start === 0 ) return { head : input . messages , tail_start_id : undefined }
153176 return {
154- head : input . messages . slice ( 0 , start ) ,
155- tail_start_id : input . messages [ start ] ?. info . id ,
177+ head : input . messages . slice ( 0 , keep . start ) ,
178+ tail_start_id : keep . id ,
156179 }
157180 } )
158181
0 commit comments