@@ -1031,10 +1031,123 @@ export class Cline extends EventEmitter<ClineEvents> {
10311031 }
10321032 }
10331033
1034- // Clean conversation history by:
1035- // 1. Converting to Anthropic.MessageParam by spreading only the API-required properties
1036- // 2. Converting image blocks to text descriptions if model doesn't support images
1037- const cleanConversationHistory = this . apiConversationHistory . map ( ( { role, content } ) => {
1034+ // Apply filtering logic to keep only the most recent environment_detail and file<open> content blocks
1035+ let latestEnvDetailsTs : number | undefined = undefined
1036+ // Key: "filePath" or "filePath:lineRange"
1037+ const latestFileOpenTimestamps = new Map < string , number > ( )
1038+ const latestWriteToFileTimestamps = new Map < string , number > ( )
1039+
1040+ // Helper function to extract file path and line range from text blocks
1041+ const extractFileInfo = ( text : string ) : { filePath : string ; fileKey : string } | null => {
1042+ try {
1043+ const pathMatch = text . match ( / < p a t h > ( .* ?) < \/ p a t h > / )
1044+ if ( ! pathMatch || ! pathMatch [ 1 ] ) return null
1045+
1046+ const filePath = pathMatch [ 1 ]
1047+ const contentMatch = text . match ( / < c o n t e n t l i n e s = " ( .* ?) " > / )
1048+ const lineRange = contentMatch && contentMatch [ 1 ] ? contentMatch [ 1 ] : null
1049+ // Use path:lines as key if lines exist, otherwise just path
1050+ const fileKey = lineRange ? `${ filePath } :${ lineRange } ` : filePath
1051+
1052+ return { filePath, fileKey }
1053+ } catch ( e ) {
1054+ console . error ( "Failed to parse file info from block:" , e )
1055+ return null
1056+ }
1057+ }
1058+
1059+ // Iterate from newest to oldest to find the latest timestamps for blocks
1060+ for ( let i = this . apiConversationHistory . length - 1 ; i >= 0 ; i -- ) {
1061+ const message = this . apiConversationHistory [ i ]
1062+ if ( ! message . ts ) continue // Skip messages without timestamps if any
1063+ if ( Array . isArray ( message . content ) ) {
1064+ for ( const block of message . content ) {
1065+ if ( block . type === "text" ) {
1066+ if ( message . role === "user" ) {
1067+ if ( block . text . startsWith ( "<environment_details>" ) && latestEnvDetailsTs === undefined ) {
1068+ latestEnvDetailsTs = message . ts
1069+ } else if ( block . text . startsWith ( "<file>" ) ) {
1070+ const fileInfo = extractFileInfo ( block . text )
1071+ if ( fileInfo && ! latestFileOpenTimestamps . has ( fileInfo . fileKey ) ) {
1072+ latestFileOpenTimestamps . set ( fileInfo . fileKey , message . ts )
1073+ }
1074+ }
1075+ } else if ( message . role === "assistant" ) {
1076+ if ( block . text . startsWith ( "<write_to_file>" ) ) {
1077+ const fileInfo = extractFileInfo ( block . text )
1078+ if ( fileInfo && ! latestWriteToFileTimestamps . has ( fileInfo . filePath ) ) {
1079+ latestWriteToFileTimestamps . set ( fileInfo . filePath , message . ts ! )
1080+ }
1081+ }
1082+ }
1083+ }
1084+ }
1085+ }
1086+ }
1087+
1088+ // Helper function to filter environment details blocks
1089+ const shouldKeepEnvDetails = ( block : Anthropic . TextBlockParam , messageTs ?: number ) : boolean => {
1090+ if ( block . text . startsWith ( "<environment_details>" ) ) {
1091+ return messageTs === latestEnvDetailsTs
1092+ }
1093+ return true
1094+ }
1095+
1096+ // Helper function to filter file blocks
1097+ const shouldKeepFileBlock = ( block : Anthropic . TextBlockParam , messageTs ?: number ) : boolean => {
1098+ if ( block . text . startsWith ( "<file>" ) ) {
1099+ const fileInfo = extractFileInfo ( block . text )
1100+ if ( fileInfo ) {
1101+ return messageTs === latestFileOpenTimestamps . get ( fileInfo . fileKey )
1102+ }
1103+ }
1104+ return true
1105+ }
1106+
1107+ // Helper function to filter write_to_file blocks
1108+ const shouldKeepWriteToFileBlock = ( block : Anthropic . TextBlockParam , messageTs ?: number ) : boolean => {
1109+ if ( block . text . startsWith ( "<write_to_file>" ) ) {
1110+ const fileInfo = extractFileInfo ( block . text )
1111+ if ( fileInfo ) {
1112+ return messageTs === latestWriteToFileTimestamps . get ( fileInfo . filePath )
1113+ }
1114+ }
1115+ return true
1116+ }
1117+
1118+ // Create a new history array with filtered content blocks
1119+ const historyWithFilteredBlocks : ( Anthropic . MessageParam & { ts ?: number } ) [ ] = [ ]
1120+ for ( const message of this . apiConversationHistory ) {
1121+ if ( message . role === "user" && Array . isArray ( message . content ) ) {
1122+ const newContent = message . content . filter ( ( block ) => {
1123+ if ( block . type === "text" ) {
1124+ return shouldKeepEnvDetails ( block , message . ts ) && shouldKeepFileBlock ( block , message . ts )
1125+ }
1126+ return true // Keep all other block types
1127+ } )
1128+ // Only add the message if it still has content
1129+ if ( newContent . length > 0 ) {
1130+ historyWithFilteredBlocks . push ( { ...message , content : newContent } )
1131+ }
1132+ } else if ( message . role === "assistant" && Array . isArray ( message . content ) ) {
1133+ const newContent = message . content . filter ( ( block ) => {
1134+ if ( block . type === "text" ) {
1135+ return shouldKeepWriteToFileBlock ( block , message . ts )
1136+ }
1137+ return true // Keep all other block types
1138+ } )
1139+ // Only add the message if it still has content
1140+ if ( newContent . length > 0 ) {
1141+ historyWithFilteredBlocks . push ( { ...message , content : newContent } )
1142+ }
1143+ } else {
1144+ // Keep other message types (like non-array content)
1145+ historyWithFilteredBlocks . push ( message )
1146+ }
1147+ }
1148+
1149+ // The existing image handling logic should be applied to historyWithFilteredBlocks
1150+ const cleanConversationHistory = historyWithFilteredBlocks . map ( ( { role, content } ) => {
10381151 // Handle array content (could contain image blocks)
10391152 if ( Array . isArray ( content ) ) {
10401153 if ( ! this . api . getModel ( ) . info . supportsImages ) {
0 commit comments