11import { randomUUID } from 'crypto' ;
2- import { resolveSessionDir , appendLine } from '../session/io.js' ;
2+ import { resolveSessionJsonlPath , appendLine } from '../session/io.js' ;
33import {
44 estimateTokens ,
55 estimateMessageTokens ,
6- estimateTokensForContent ,
76} from './util.js' ;
8- import { applyVisibilityEvents } from '../session/messages.js' ;
7+ import { buildMessagesFromEvents } from '../session/messages.js' ;
98import { resolveLLM } from '../llm/llm-resolver.js' ;
109import { COMPACTION_SYSTEM_PROMPT } from './compaction-prompt.js' ;
1110import type { ContextConfig } from './config.js' ;
1211import type { Message } from '../core/types.js' ;
1312import type { SessionEvent , SummaryEvent } from '../session/types.js' ;
1413import type { LLMClient } from '../llm/client.js' ;
1514import { assemblePayload } from './organizer.js' ;
16- import { join } from 'path' ;
1715
1816export interface CompressResult {
1917 didCompress : boolean ;
@@ -85,8 +83,8 @@ export async function compactWithLLM(
8583 usage ?: number ,
8684 modelMaxTokens ?: number
8785) : Promise < CompressResult > {
86+ const payload = assemblePayload ( sessionId , encodedProjectPath , config , modelMaxTokens ) ;
8887 if ( ! compactedEvents || currentTurnId === undefined ) {
89- const payload = assemblePayload ( sessionId , encodedProjectPath , config , modelMaxTokens ) ;
9088 compactedEvents = payload . compactedEvents ;
9189 currentTurnId = payload . currentTurnId ;
9290 }
@@ -95,38 +93,33 @@ export async function compactWithLLM(
9593
9694 const threshold = modelMaxTokens ? modelMaxTokens * config . compactionThreshold : Infinity ;
9795 if ( usage === undefined || usage - released > threshold ) {
98- released += await tryCompaction ( sessionId , config , llm , compactedEvents , currentTurnId ) ;
96+ released += await tryCompaction ( sessionId , config , llm , compactedEvents , currentTurnId , payload . compactedTurnIds ) ;
9997 }
10098
101- const payload = assemblePayload ( sessionId , encodedProjectPath , config , modelMaxTokens ) ;
99+ const postPayload = assemblePayload ( sessionId , encodedProjectPath , config , modelMaxTokens ) ;
102100 return {
103101 didCompress : released > 0 ,
104102 released,
105- promptEstimate : estimateTokens ( payload . messages ) ,
103+ promptEstimate : estimateTokens ( postPayload . messages ) ,
106104 } ;
107105}
108106
109107
110108// ---------- LLM Compaction ----------
111109
112- const ESTIMATED_SUMMARY_TOKENS = 5000 ;
113- const MAX_TOOL_RESULT_TOKENS = 30000 ;
114-
115110async function tryCompaction (
116111 sessionId : string ,
117112 config : ContextConfig ,
118113 llm : LLMClient | null ,
119114 compactedEvents : SessionEvent [ ] ,
120- currentTurnId : number
115+ currentTurnId : number ,
116+ compactedTurnIds : Set < number > ,
121117) : Promise < number > {
122118 const endTurn = currentTurnId - config . keepRecentTurns - 1 ;
123119 if ( endTurn < 1 ) return 0 ;
124120
125- const { hidden } = applyVisibilityEvents ( compactedEvents ) ;
126-
127121 const inRange = compactedEvents . filter ( ( ev ) => {
128122 if ( ev . type === 'session_meta' ) return false ;
129- if ( 'uuid' in ev && hidden . has ( ( ev as any ) . uuid ) ) return false ;
130123 if ( 'turnId' in ev && ( ev as any ) . turnId >= 1 && ( ev as any ) . turnId <= endTurn ) return true ;
131124 return false ;
132125 } ) ;
@@ -135,15 +128,15 @@ async function tryCompaction(
135128 const targetEvents = getIncrementalEvents ( inRange ) ;
136129 if ( targetEvents . length === 0 ) return 0 ;
137130
138- const totalTokens = targetEvents . reduce ( ( sum , e ) => sum + estimateEventTokens ( e ) , 0 ) ;
131+ const msgs = buildMessagesFromEvents ( targetEvents , compactedTurnIds ) ;
132+ const totalTokens = estimateTokens ( msgs ) ;
139133
140134 let compactionLlm = await resolveLLM ( config . compactionModel , llm ) ;
141135 if ( compactionLlm && compactionLlm . modelInfo . maxTokens < totalTokens + 25000 ) {
142136 compactionLlm = llm ;
143137 }
144138
145- const transcript = buildTranscript ( targetEvents ) ;
146- const summary = await callLLMForCompaction ( transcript , compactionLlm , config ) ;
139+ const summary = await callLLMForCompaction ( msgs , compactionLlm , config ) ;
147140 if ( ! summary ) return 0 ;
148141
149142 const replacedUuids : string [ ] = [ ] ;
@@ -164,10 +157,7 @@ async function tryCompaction(
164157 lastSummarizedTurnId : lastTurnId ,
165158 timestamp : new Date ( ) . toISOString ( ) ,
166159 } ;
167- const dir = resolveSessionDir ( sessionId ) ;
168- if ( ! dir ) throw new Error ( `Session ${ sessionId } not found` ) ;
169- appendLine ( join ( dir , `${ sessionId } .jsonl` ) , event ) ;
170- for ( const u of replacedUuids ) hidden . add ( u ) ;
160+ appendLine ( resolveSessionJsonlPath ( sessionId ) , event ) ;
171161
172162 const summaryMsg : Message = { role : 'system' , name : 'compacted_history' , content : summary } ;
173163 return Math . max ( 0 , totalTokens - estimateMessageTokens ( summaryMsg ) ) ;
@@ -184,64 +174,6 @@ function getIncrementalEvents(inRange: SessionEvent[]): SessionEvent[] {
184174 return inRange . filter ( ( e ) => 'turnId' in e && ( e as any ) . turnId > lastTurn ) ;
185175}
186176
187- function buildTranscript ( events : SessionEvent [ ] ) : Message [ ] {
188- const transcript : Message [ ] = [ ] ;
189- for ( const ev of events ) {
190- switch ( ev . type ) {
191- case 'user' :
192- transcript . push ( { role : 'user' , content : ev . content } ) ;
193- break ;
194- case 'assistant' :
195- transcript . push ( { role : 'assistant' , content : ev . content } ) ;
196- break ;
197- case 'tool_result' : {
198- let content = ev . output ;
199- const tokens = estimateTokensForContent ( content ) ;
200- if ( tokens > MAX_TOOL_RESULT_TOKENS ) {
201- const ratio = MAX_TOOL_RESULT_TOKENS / tokens ;
202- const keepChars = Math . floor ( content . length * ratio ) ;
203- content =
204- content . slice ( 0 , keepChars ) +
205- `\n\n[...truncated: ${ tokens } tokens total, showing first ${ MAX_TOOL_RESULT_TOKENS } ]` ;
206- }
207- transcript . push ( {
208- role : 'tool' ,
209- content,
210- tool_call_id : ev . toolCallId ,
211- tool_name : ev . toolName ,
212- } as any ) ;
213- break ;
214- }
215- case 'summary' :
216- transcript . push ( { role : 'system' , name : 'compacted_history' , content : ev . summaryText } ) ;
217- break ;
218- }
219- }
220- return transcript ;
221- }
222-
223- function estimateEventTokens ( e : SessionEvent ) : number {
224- if ( e . type === 'user' ) return estimateMessageTokens ( { role : 'user' , content : e . content } ) ;
225- if ( e . type === 'assistant' )
226- return estimateMessageTokens ( { role : 'assistant' , content : e . content } ) ;
227- if ( e . type === 'tool_result' ) {
228- return estimateMessageTokens ( {
229- role : 'tool' ,
230- content : e . output ,
231- tool_call_id : e . toolCallId ,
232- tool_name : e . toolName ,
233- } as any ) ;
234- }
235- if ( e . type === 'summary' ) {
236- return estimateMessageTokens ( {
237- role : 'system' ,
238- name : 'compacted_history' ,
239- content : e . summaryText ,
240- } ) ;
241- }
242- return 0 ;
243- }
244-
245177async function callLLMForCompaction (
246178 transcript : Message [ ] ,
247179 fallbackLlm : LLMClient | null ,
@@ -251,7 +183,7 @@ async function callLLMForCompaction(
251183 if ( ! llm ) return null ;
252184
253185 const transcriptText = transcript
254- . map ( ( m ) => `[${ m . role } ${ m . tool_name ? ':' + m . tool_name : '' } ]\n${ m . content } ` )
186+ . map ( ( m ) => `[${ m . role } ${ ( m as any ) . tool_name ? ':' + ( m as any ) . tool_name : '' } ]\n${ m . content } ` )
255187 . join ( '\n\n' ) ;
256188
257189 const system = COMPACTION_SYSTEM_PROMPT ;
0 commit comments