@@ -647,6 +647,11 @@ function excerptTailText(
647647 } ;
648648}
649649
650+ let excerptTailTurnCallCountValue = 0 ;
651+ export function excerptTailTurnCallCount ( ) : number {
652+ return excerptTailTurnCallCountValue ;
653+ }
654+
650655// Excerpted live copy of a tail turn: thinking/redacted_thinking drop except
651656// on the last assistant with a tool_call (Anthropic continuation needs the
652657// unmodified thinking+signature on that tool_use). Whole blocks drop so
@@ -658,6 +663,7 @@ function excerptTailTurn(
658663 shape : CompactionShape ,
659664 keepThinking = false ,
660665) : { turn : ConversationTurn ; shortenedOutputs : number ; changed : boolean } {
666+ excerptTailTurnCallCountValue += 1 ;
661667 let shortenedOutputs = 0 ;
662668 let changed = false ;
663669 const content = turn . content . flatMap ( ( block ) : ConversationTurn [ "content" ] => {
@@ -716,12 +722,7 @@ function lastAssistantToolCallIndex(
716722 return undefined ;
717723}
718724
719- function excerptedTurnTokens (
720- turn : ConversationTurn ,
721- shape : CompactionShape ,
722- keepThinking = false ,
723- ) : number {
724- const { turn : live } = excerptTailTurn ( turn , shape , keepThinking ) ;
725+ function liveTurnTokens ( live : ConversationTurn ) : number {
725726 let tokens = 0 ;
726727 for ( const block of live . content ) {
727728 tokens += estimateContentBlockTokens ( block ) ;
@@ -769,18 +770,24 @@ function selectTail(
769770 const budgetTokens = shape . tailBudgetTokens ;
770771 const keepThinkingAt = lastAssistantToolCallIndex ( turns ) ;
771772
773+ type ExcerptResult = ReturnType < typeof excerptTailTurn > ;
774+ const excerptCache = new Map < number , ExcerptResult > ( ) ;
775+ const excerptFor = ( idx : number , turn : ConversationTurn ) : ExcerptResult => {
776+ let result = excerptCache . get ( idx ) ;
777+ if ( result === undefined ) {
778+ result = excerptTailTurn ( turn , shape , idx === keepThinkingAt ) ;
779+ excerptCache . set ( idx , result ) ;
780+ }
781+ return result ;
782+ } ;
783+
772784 const turnCost = ( idx : number ) : { tokens : number ; shortened : number } => {
773785 const turn = turns [ idx ] ;
774786 if ( turn === undefined ) return { tokens : 0 , shortened : 0 } ;
775- const keepThinking = idx === keepThinkingAt ;
776- const {
777- turn : live ,
778- shortenedOutputs,
779- changed,
780- } = excerptTailTurn ( turn , shape , keepThinking ) ;
787+ const { turn : live , shortenedOutputs, changed } = excerptFor ( idx , turn ) ;
781788 if ( changed ) excerpted . set ( idx , live ) ;
782789 return {
783- tokens : excerptedTurnTokens ( turn , shape , keepThinking ) ,
790+ tokens : liveTurnTokens ( live ) ,
784791 shortened : shortenedOutputs ,
785792 } ;
786793 } ;
@@ -836,7 +843,7 @@ function selectTail(
836843 for ( const idx of closure ) {
837844 const t = turns [ idx ] ;
838845 if ( t === undefined || isFoldableHandoffTurn ( t ) ) continue ;
839- closureTokens += excerptedTurnTokens ( t , shape , idx === keepThinkingAt ) ;
846+ closureTokens += liveTurnTokens ( excerptFor ( idx , t ) . turn ) ;
840847 }
841848 if ( picked . size > 0 && usedTokens + closureTokens > budgetTokens ) break ;
842849 for ( const idx of closure ) pick ( idx ) ;
0 commit comments