@@ -16,6 +16,7 @@ import {
1616} from "./messages"
1717import { renderSystemPrompt , type PromptStore } from "./prompts"
1818import { buildProtectedToolsExtension } from "./prompts/extensions/system"
19+ import { attachCompressionDuration , recordCompressionDuration } from "./compress/state"
1920import {
2021 applyPendingManualTrigger ,
2122 handleContextCommand ,
@@ -29,7 +30,7 @@ import {
2930} from "./commands"
3031import { type HostPermissionSnapshot } from "./host-permissions"
3132import { compressPermission , syncCompressPermissionState } from "./compress-permission"
32- import { checkSession , ensureSessionInitialized , syncToolCache } from "./state"
33+ import { checkSession , ensureSessionInitialized , saveSessionState , syncToolCache } from "./state"
3334import { cacheSystemPromptTokens } from "./ui/utils"
3435
3536const INTERNAL_AGENT_SIGNATURES = [
@@ -266,6 +267,135 @@ export function createTextCompleteHandler() {
266267 }
267268}
268269
270+ export function createEventHandler ( state : SessionState , logger : Logger ) {
271+ return async ( input : { event : any } ) => {
272+ const eventTime =
273+ typeof input . event ?. time === "number" && Number . isFinite ( input . event . time )
274+ ? input . event . time
275+ : typeof input . event ?. properties ?. time === "number" &&
276+ Number . isFinite ( input . event . properties . time )
277+ ? input . event . properties . time
278+ : undefined
279+
280+ if ( input . event . type !== "message.part.updated" ) {
281+ return
282+ }
283+
284+ const part = input . event . properties ?. part
285+ if ( part ?. type !== "tool" || part . tool !== "compress" ) {
286+ return
287+ }
288+
289+ if ( part . state . status === "pending" ) {
290+ if ( typeof part . callID !== "string" || typeof part . messageID !== "string" ) {
291+ return
292+ }
293+
294+ if ( state . compressionStarts . has ( part . callID ) ) {
295+ return
296+ }
297+
298+ const startedAt = eventTime ?? Date . now ( )
299+ state . compressionStarts . set ( part . callID , {
300+ messageId : part . messageID ,
301+ startedAt,
302+ } )
303+ logger . debug ( "Recorded compression start" , {
304+ callID : part . callID ,
305+ messageID : part . messageID ,
306+ startedAt,
307+ } )
308+ return
309+ }
310+
311+ if ( part . state . status === "running" ) {
312+ if ( typeof part . callID !== "string" ) {
313+ return
314+ }
315+
316+ const start = state . compressionStarts . get ( part . callID )
317+ if ( ! start ) {
318+ return
319+ }
320+
321+ const runningAt =
322+ typeof part . state . time ?. start === "number" && Number . isFinite ( part . state . time . start )
323+ ? part . state . time . start
324+ : eventTime
325+ if ( typeof runningAt !== "number" ) {
326+ return
327+ }
328+
329+ state . compressionStarts . delete ( part . callID )
330+ const durationMs = Math . max ( 0 , runningAt - start . startedAt )
331+ recordCompressionDuration ( state , part . callID , durationMs )
332+
333+ logger . info ( "Recorded compression time" , {
334+ callID : part . callID ,
335+ messageID : start . messageId ,
336+ durationMs,
337+ } )
338+ return
339+ }
340+
341+ if ( part . state . status === "completed" ) {
342+ if ( typeof part . callID !== "string" || typeof part . messageID !== "string" ) {
343+ return
344+ }
345+
346+ if ( ! state . compressionDurations . has ( part . callID ) ) {
347+ const start = state . compressionStarts . get ( part . callID )
348+ const runningAt =
349+ typeof part . state . time ?. start === "number" &&
350+ Number . isFinite ( part . state . time . start )
351+ ? part . state . time . start
352+ : eventTime
353+
354+ if ( start && typeof runningAt === "number" ) {
355+ state . compressionStarts . delete ( part . callID )
356+ const durationMs = Math . max ( 0 , runningAt - start . startedAt )
357+ recordCompressionDuration ( state , part . callID , durationMs )
358+ } else {
359+ const toolStart = part . state . time ?. start
360+ const toolEnd = part . state . time ?. end
361+ if (
362+ typeof toolStart === "number" &&
363+ Number . isFinite ( toolStart ) &&
364+ typeof toolEnd === "number" &&
365+ Number . isFinite ( toolEnd )
366+ ) {
367+ const durationMs = Math . max ( 0 , toolEnd - toolStart )
368+ recordCompressionDuration ( state , part . callID , durationMs )
369+ }
370+ }
371+ }
372+
373+ const updates = attachCompressionDuration ( state , part . callID , part . messageID )
374+ if ( updates === 0 ) {
375+ return
376+ }
377+
378+ logger . info ( "Attached compression time to blocks" , {
379+ callID : part . callID ,
380+ messageID : part . messageID ,
381+ blocks : updates ,
382+ } )
383+
384+ saveSessionState ( state , logger ) . catch ( ( error ) => {
385+ logger . warn ( "Failed to persist compression time update" , {
386+ error : error instanceof Error ? error . message : String ( error ) ,
387+ } )
388+ } )
389+ return
390+ }
391+
392+ if ( typeof part . callID === "string" ) {
393+ state . compressionStarts . delete ( part . callID )
394+ state . compressionDurations . delete ( part . callID )
395+ }
396+ }
397+ }
398+
269399export function createChatMessageHandler (
270400 state : SessionState ,
271401 logger : Logger ,
0 commit comments