@@ -426,6 +426,8 @@ pub enum ChatError {
426
426
"Tool approval required but --no-interactive was specified. Use --trust-all-tools to automatically approve tools."
427
427
) ]
428
428
NonInteractiveToolApproval ,
429
+ #[ error( "The conversation history is too large to compact" ) ]
430
+ CompactHistoryFailure ,
429
431
}
430
432
431
433
impl ChatError {
@@ -440,6 +442,7 @@ impl ChatError {
440
442
ChatError :: Interrupted { .. } => None ,
441
443
ChatError :: GetPromptError ( _) => None ,
442
444
ChatError :: NonInteractiveToolApproval => None ,
445
+ ChatError :: CompactHistoryFailure => None ,
443
446
}
444
447
}
445
448
}
@@ -456,6 +459,7 @@ impl ReasonCode for ChatError {
456
459
ChatError :: GetPromptError ( _) => "GetPromptError" . to_string ( ) ,
457
460
ChatError :: Auth ( _) => "AuthError" . to_string ( ) ,
458
461
ChatError :: NonInteractiveToolApproval => "NonInteractiveToolApproval" . to_string ( ) ,
462
+ ChatError :: CompactHistoryFailure => "CompactHistoryFailure" . to_string ( ) ,
459
463
}
460
464
}
461
465
}
@@ -618,9 +622,13 @@ impl ChatSession {
618
622
Ok ( _) = ctrl_c_stream => Err ( ChatError :: Interrupted { tool_uses: Some ( self . tool_uses. clone( ) ) } )
619
623
}
620
624
} ,
621
- ChatState :: CompactHistory { prompt, show_summary } => {
625
+ ChatState :: CompactHistory {
626
+ prompt,
627
+ show_summary,
628
+ attempt_truncated_compact_retry,
629
+ } => {
622
630
tokio:: select! {
623
- res = self . compact_history( os, prompt, show_summary) => res,
631
+ res = self . compact_history( os, prompt, show_summary, attempt_truncated_compact_retry ) => res,
624
632
Ok ( _) = ctrl_c_stream => Err ( ChatError :: Interrupted { tool_uses: Some ( self . tool_uses. clone( ) ) } )
625
633
}
626
634
} ,
@@ -697,40 +705,40 @@ impl ChatSession {
697
705
698
706
( "Tool use was interrupted" , Report :: from ( err) , false )
699
707
} ,
708
+ ChatError :: CompactHistoryFailure => {
709
+ // This error is not retryable - the user must take manual intervention to manage
710
+ // their context.
711
+ execute ! (
712
+ self . stderr,
713
+ style:: SetForegroundColor ( Color :: Red ) ,
714
+ style:: Print ( "Your conversation is too large to continue.\n " ) ,
715
+ style:: SetForegroundColor ( Color :: Reset ) ,
716
+ style:: Print ( format!( "• Run {} to analyze your context usage\n " , "/usage" . green( ) ) ) ,
717
+ style:: Print ( format!( "• Run {} to reset your conversation state\n " , "/clear" . green( ) ) ) ,
718
+ style:: SetAttribute ( Attribute :: Reset ) ,
719
+ style:: Print ( "\n \n " ) ,
720
+ ) ?;
721
+ ( "Unable to compact the conversation history" , eyre ! ( err) , true )
722
+ } ,
700
723
ChatError :: Client ( err) => match * err {
701
724
// Errors from attempting to send too large of a conversation history. In
702
725
// this case, attempt to automatically compact the history for the user.
703
726
ApiClientError :: ContextWindowOverflow { .. } => {
704
- if !self . conversation . can_create_summary_request ( os) . await ? {
705
- execute ! (
706
- self . stderr,
707
- style:: SetForegroundColor ( Color :: Red ) ,
708
- style:: Print ( "Your conversation is too large to continue.\n " ) ,
709
- style:: SetForegroundColor ( Color :: Reset ) ,
710
- style:: Print ( format!( "• Run {} to analyze your context usage\n " , "/usage" . green( ) ) ) ,
711
- style:: Print ( format!( "• Run {} to reset your conversation state\n " , "/clear" . green( ) ) ) ,
712
- style:: SetAttribute ( Attribute :: Reset ) ,
713
- style:: Print ( "\n \n " ) ,
714
- ) ?;
715
-
716
- self . conversation . reset_next_user_message ( ) ;
717
- self . inner = Some ( ChatState :: PromptUser {
718
- skip_printing_tools : false ,
719
- } ) ;
720
-
721
- return Ok ( ( ) ) ;
722
- }
723
-
724
727
self . inner = Some ( ChatState :: CompactHistory {
725
728
prompt : None ,
726
729
show_summary : false ,
730
+ attempt_truncated_compact_retry : true ,
727
731
} ) ;
728
732
729
- (
730
- "The context window has overflowed, summarizing the history..." ,
731
- Report :: from ( err) ,
732
- true ,
733
- )
733
+ execute ! (
734
+ self . stdout,
735
+ style:: SetForegroundColor ( Color :: Yellow ) ,
736
+ style:: Print ( "The context window has overflowed, summarizing the history..." ) ,
737
+ style:: SetAttribute ( Attribute :: Reset ) ,
738
+ style:: Print ( "\n \n " ) ,
739
+ ) ?;
740
+
741
+ return Ok ( ( ) ) ;
734
742
} ,
735
743
ApiClientError :: QuotaBreach { message, .. } => ( message, Report :: from ( err) , true ) ,
736
744
ApiClientError :: ModelOverloadedError { request_id, .. } => {
@@ -890,6 +898,11 @@ enum ChatState {
890
898
prompt : Option < String > ,
891
899
/// Whether or not the summary should be shown on compact success.
892
900
show_summary : bool ,
901
+ /// Whether or not we should truncate large messages in the conversation history if we
902
+ /// encounter a context window overfload while attempting compaction.
903
+ ///
904
+ /// This should be `true` everywhere other than [ChatSession::compact_history].
905
+ attempt_truncated_compact_retry : bool ,
893
906
} ,
894
907
/// Exit the chat.
895
908
Exit ,
@@ -995,17 +1008,21 @@ impl ChatSession {
995
1008
/// Compacts the conversation history, replacing the history with a summary generated by the
996
1009
/// model.
997
1010
///
998
- /// The last two user messages in the history are not included in the compaction process.
1011
+ /// If `attempt_truncated_compact_retry` is true, then if we encounter a context window
1012
+ /// overflow while attempting compaction, large user messages will be heavily truncated and
1013
+ /// the compaction attempt will be retried, failing with [ChatError::CompactHistoryFailure] if
1014
+ /// we fail again.
999
1015
async fn compact_history (
1000
1016
& mut self ,
1001
1017
os : & Os ,
1002
1018
custom_prompt : Option < String > ,
1003
1019
show_summary : bool ,
1020
+ attempt_truncated_compact_retry : bool ,
1004
1021
) -> Result < ChatState , ChatError > {
1005
1022
let hist = self . conversation . history ( ) ;
1006
1023
debug ! ( ?hist, "compacting history" ) ;
1007
1024
1008
- if self . conversation . history ( ) . len ( ) < 2 {
1025
+ if self . conversation . history ( ) . is_empty ( ) {
1009
1026
execute ! (
1010
1027
self . stderr,
1011
1028
style:: SetForegroundColor ( Color :: Yellow ) ,
@@ -1046,23 +1063,29 @@ impl ChatSession {
1046
1063
. await ;
1047
1064
match err {
1048
1065
ApiClientError :: ContextWindowOverflow { .. } => {
1049
- self . conversation . clear ( true ) ;
1050
-
1051
- self . spinner . take ( ) ;
1052
- execute ! (
1053
- self . stderr,
1054
- terminal:: Clear ( terminal:: ClearType :: CurrentLine ) ,
1055
- cursor:: MoveToColumn ( 0 ) ,
1056
- style:: SetForegroundColor ( Color :: Yellow ) ,
1057
- style:: Print (
1058
- "The context window usage has overflowed. Clearing the conversation history.\n \n "
1059
- ) ,
1060
- style:: SetAttribute ( Attribute :: Reset )
1061
- ) ?;
1062
-
1063
- return Ok ( ChatState :: PromptUser {
1064
- skip_printing_tools : true ,
1065
- } ) ;
1066
+ error ! ( ?attempt_truncated_compact_retry, "failed to send compaction request" ) ;
1067
+ if attempt_truncated_compact_retry {
1068
+ self . conversation . truncate_large_user_messages ( ) . await ;
1069
+ if self . spinner . is_some ( ) {
1070
+ drop ( self . spinner . take ( ) ) ;
1071
+ execute ! (
1072
+ self . stderr,
1073
+ terminal:: Clear ( terminal:: ClearType :: CurrentLine ) ,
1074
+ cursor:: MoveToColumn ( 0 ) ,
1075
+ style:: SetForegroundColor ( Color :: Yellow ) ,
1076
+ style:: Print ( "Reducing context..." ) ,
1077
+ style:: SetAttribute ( Attribute :: Reset ) ,
1078
+ style:: Print ( "\n \n " ) ,
1079
+ ) ?;
1080
+ }
1081
+ return Ok ( ChatState :: CompactHistory {
1082
+ prompt : custom_prompt,
1083
+ show_summary,
1084
+ attempt_truncated_compact_retry : false ,
1085
+ } ) ;
1086
+ } else {
1087
+ return Err ( ChatError :: CompactHistoryFailure ) ;
1088
+ }
1066
1089
} ,
1067
1090
err => return Err ( err. into ( ) ) ,
1068
1091
}
@@ -1195,10 +1218,8 @@ impl ChatSession {
1195
1218
// Check token usage and display warnings if needed
1196
1219
if self . pending_tool_index . is_none ( ) {
1197
1220
// Only display warnings when not waiting for tool approval
1198
- if self . conversation . can_create_summary_request ( os) . await ? {
1199
- if let Err ( err) = self . display_char_warnings ( os) . await {
1200
- warn ! ( "Failed to display character limit warnings: {}" , err) ;
1201
- }
1221
+ if let Err ( err) = self . display_char_warnings ( os) . await {
1222
+ warn ! ( "Failed to display character limit warnings: {}" , err) ;
1202
1223
}
1203
1224
}
1204
1225
0 commit comments