@@ -33,6 +33,7 @@ use super::message::{
3333 ToolUseResult ,
3434 UserMessage ,
3535} ;
36+ use super :: parser:: RequestMetadata ;
3637use super :: token_counter:: {
3738 CharCount ,
3839 CharCounter ,
@@ -65,6 +66,8 @@ use crate::os::Os;
6566const CONTEXT_ENTRY_START_HEADER : & str = "--- CONTEXT ENTRY BEGIN ---\n " ;
6667const CONTEXT_ENTRY_END_HEADER : & str = "--- CONTEXT ENTRY END ---\n \n " ;
6768
69+ pub type HistoryEntry = ( UserMessage , AssistantMessage , Option < RequestMetadata > ) ;
70+
6871/// Tracks state related to an ongoing conversation.
6972#[ derive( Debug , Clone , Serialize , Deserialize ) ]
7073pub struct ConversationState {
@@ -73,7 +76,7 @@ pub struct ConversationState {
7376 /// The next user message to be sent as part of the conversation. Required to be [Some] before
7477 /// calling [Self::as_sendable_conversation_state].
7578 next_message : Option < UserMessage > ,
76- history : VecDeque < ( UserMessage , AssistantMessage ) > ,
79+ history : VecDeque < HistoryEntry > ,
7780 /// The range in the history sendable to the backend (start inclusive, end exclusive).
7881 valid_history_range : ( usize , usize ) ,
7982 /// Similar to history in that stores user and assistant responses, except that it is not used
@@ -90,7 +93,7 @@ pub struct ConversationState {
9093 /// Cached value representing the length of the user context message.
9194 context_message_length : Option < usize > ,
9295 /// Stores the latest conversation summary created by /compact
93- latest_summary : Option < String > ,
96+ latest_summary : Option < ( String , RequestMetadata ) > ,
9497 /// Model explicitly selected by the user in this conversation state via `/model`.
9598 #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
9699 pub model : Option < String > ,
@@ -180,10 +183,10 @@ impl ConversationState {
180183 }
181184
182185 pub fn latest_summary ( & self ) -> Option < & str > {
183- self . latest_summary . as_deref ( )
186+ self . latest_summary . as_ref ( ) . map ( | ( s , _ ) | s . as_str ( ) )
184187 }
185188
186- pub fn history ( & self ) -> & VecDeque < ( UserMessage , AssistantMessage ) > {
189+ pub fn history ( & self ) -> & VecDeque < HistoryEntry > {
187190 & self . history
188191 }
189192
@@ -219,7 +222,7 @@ impl ConversationState {
219222 let asst = candidate_asst. take ( ) . unwrap ( ) ;
220223 let user = candidate_user. take ( ) . unwrap ( ) ;
221224 self . append_assistant_transcript ( & asst) ;
222- self . history . push_back ( ( user, asst) ) ;
225+ self . history . push_back ( ( user, asst, None ) ) ;
223226 }
224227 }
225228 Some ( last_msg. content . to_string ( ) )
@@ -251,12 +254,17 @@ impl ConversationState {
251254 }
252255
253256 /// Sets the response message according to the currently set [Self::next_message].
254- pub fn push_assistant_message ( & mut self , os : & mut Os , message : AssistantMessage ) {
257+ pub fn push_assistant_message (
258+ & mut self ,
259+ os : & mut Os ,
260+ message : AssistantMessage ,
261+ request_metadata : Option < RequestMetadata > ,
262+ ) {
255263 debug_assert ! ( self . next_message. is_some( ) , "next_message should exist" ) ;
256264 let next_user_message = self . next_message . take ( ) . expect ( "next user message should exist" ) ;
257265
258266 self . append_assistant_transcript ( & message) ;
259- self . history . push_back ( ( next_user_message, message) ) ;
267+ self . history . push_back ( ( next_user_message, message, request_metadata ) ) ;
260268
261269 if let Ok ( cwd) = std:: env:: current_dir ( ) {
262270 os. database . set_conversation_by_path ( cwd, self ) . ok ( ) ;
@@ -272,7 +280,21 @@ impl ConversationState {
272280 ///
273281 /// This is equivalent to `utterance_id` in the Q API.
274282 pub fn message_id ( & self ) -> Option < & str > {
275- self . history . back ( ) . and_then ( |( _, msg) | msg. message_id ( ) )
283+ self . history . back ( ) . and_then ( |( _, msg, _) | msg. message_id ( ) )
284+ }
285+
286+ pub fn latest_tool_use_ids ( & self ) -> Option < String > {
287+ self . history
288+ . back ( )
289+ . and_then ( |( _, assistant, _) | assistant. tool_uses ( ) )
290+ . map ( |tools| ( tools. iter ( ) . map ( |t| t. id . as_str ( ) ) . collect :: < Vec < _ > > ( ) . join ( "," ) ) )
291+ }
292+
293+ pub fn latest_tool_use_names ( & self ) -> Option < String > {
294+ self . history
295+ . back ( )
296+ . and_then ( |( _, assistant, _) | assistant. tool_uses ( ) )
297+ . map ( |tools| ( tools. iter ( ) . map ( |t| t. name . as_str ( ) ) . collect :: < Vec < _ > > ( ) . join ( "," ) ) )
276298 }
277299
278300 /// Updates the history so that, when non-empty, the following invariants are in place:
@@ -476,7 +498,7 @@ impl ConversationState {
476498 FILTER OUT CHAT CONVENTIONS (greetings, offers to help, etc).". to_string ( )
477499 } ,
478500 } ;
479- if let Some ( summary) = & self . latest_summary {
501+ if let Some ( ( summary, _ ) ) = & self . latest_summary {
480502 summary_content. push_str ( "\n \n " ) ;
481503 summary_content. push_str ( CONTEXT_ENTRY_START_HEADER ) ;
482504 summary_content. push_str ( "This summary contains ALL relevant information from our previous conversation including tool uses, results, code analysis, and file operations. YOU MUST be sure to include this information when creating your summarization document.\n \n " ) ;
@@ -493,7 +515,7 @@ impl ConversationState {
493515 let mut history = conv_state. history . cloned ( ) . collect :: < VecDeque < _ > > ( ) ;
494516 history. drain ( ( history. len ( ) . saturating_sub ( strategy. messages_to_exclude ) ) ..) ;
495517 if strategy. truncate_large_messages {
496- for ( user_message, _) in & mut history {
518+ for ( user_message, _, _ ) in & mut history {
497519 user_message. truncate_safe ( strategy. max_message_length ) ;
498520 }
499521 }
@@ -524,10 +546,15 @@ impl ConversationState {
524546
525547 /// `strategy` - The [CompactStrategy] used for the corresponding
526548 /// [ConversationState::create_summary_request].
527- pub fn replace_history_with_summary ( & mut self , summary : String , strategy : CompactStrategy ) {
549+ pub fn replace_history_with_summary (
550+ & mut self ,
551+ summary : String ,
552+ strategy : CompactStrategy ,
553+ request_metadata : RequestMetadata ,
554+ ) {
528555 self . history
529556 . drain ( ..( self . history . len ( ) . saturating_sub ( strategy. messages_to_exclude ) ) ) ;
530- self . latest_summary = Some ( summary) ;
557+ self . latest_summary = Some ( ( summary, request_metadata ) ) ;
531558 }
532559
533560 pub fn current_profile ( & self ) -> Option < & str > {
@@ -550,10 +577,10 @@ impl ConversationState {
550577 & mut self ,
551578 os : & Os ,
552579 conversation_start_context : Option < String > ,
553- ) -> ( Option < Vec < ( UserMessage , AssistantMessage ) > > , Vec < ( String , String ) > ) {
580+ ) -> ( Option < Vec < HistoryEntry > > , Vec < ( String , String ) > ) {
554581 let mut context_content = String :: new ( ) ;
555582 let mut dropped_context_files = Vec :: new ( ) ;
556- if let Some ( summary) = & self . latest_summary {
583+ if let Some ( ( summary, _ ) ) = & self . latest_summary {
557584 context_content. push_str ( CONTEXT_ENTRY_START_HEADER ) ;
558585 context_content. push_str ( "This summary contains ALL relevant information from our previous conversation including tool uses, results, code analysis, and file operations. YOU MUST reference this information when answering questions and explicitly acknowledge specific details from the summary when they're relevant to the current question.\n \n " ) ;
559586 context_content. push_str ( "SUMMARY CONTENT:\n " ) ;
@@ -592,7 +619,7 @@ impl ConversationState {
592619 self . context_message_length = Some ( context_content. len ( ) ) ;
593620 let user_msg = UserMessage :: new_prompt ( context_content) ;
594621 let assistant_msg = AssistantMessage :: new_response ( None , "I will fully incorporate this information when generating my responses, and explicitly acknowledge relevant parts of the summary when answering questions." . into ( ) ) ;
595- ( Some ( vec ! [ ( user_msg, assistant_msg) ] ) , dropped_context_files)
622+ ( Some ( vec ! [ ( user_msg, assistant_msg, None ) ] ) , dropped_context_files)
596623 } else {
597624 ( None , dropped_context_files)
598625 }
@@ -647,11 +674,8 @@ impl ConversationState {
647674///
648675/// This is intended to provide us ways to accurately assess the exact state that is sent to the
649676/// model without having to needlessly clone and mutate [ConversationState] in strange ways.
650- pub type BackendConversationState < ' a > = BackendConversationStateImpl <
651- ' a ,
652- std:: collections:: vec_deque:: Iter < ' a , ( UserMessage , AssistantMessage ) > ,
653- Option < Vec < ( UserMessage , AssistantMessage ) > > ,
654- > ;
677+ pub type BackendConversationState < ' a > =
678+ BackendConversationStateImpl < ' a , std:: collections:: vec_deque:: Iter < ' a , HistoryEntry > , Option < Vec < HistoryEntry > > > ;
655679
656680/// See [BackendConversationState]
657681#[ derive( Debug , Clone ) ]
@@ -665,13 +689,7 @@ pub struct BackendConversationStateImpl<'a, T, U> {
665689 pub model_id : Option < & ' a str > ,
666690}
667691
668- impl
669- BackendConversationStateImpl <
670- ' _ ,
671- std:: collections:: vec_deque:: Iter < ' _ , ( UserMessage , AssistantMessage ) > ,
672- Option < Vec < ( UserMessage , AssistantMessage ) > > ,
673- >
674- {
692+ impl BackendConversationStateImpl < ' _ , std:: collections:: vec_deque:: Iter < ' _ , HistoryEntry > , Option < Vec < HistoryEntry > > > {
675693 fn into_fig_conversation_state ( self ) -> eyre:: Result < FigConversationState > {
676694 let history = flatten_history ( self . context_messages . unwrap_or_default ( ) . iter ( ) . chain ( self . history ) ) ;
677695 let user_input_message: UserInputMessage = self
695713 // Count the chars used by the messages in the history.
696714 // this clone is cheap
697715 let history = self . history . clone ( ) ;
698- for ( user, assistant) in history {
716+ for ( user, assistant, _ ) in history {
699717 user_chars += * user. char_count ( ) ;
700718 assistant_chars += * assistant. char_count ( ) ;
701719 }
705723 . context_messages
706724 . as_ref ( )
707725 . map ( |v| {
708- v. iter ( ) . fold ( 0 , |acc, ( user, assistant) | {
726+ v. iter ( ) . fold ( 0 , |acc, ( user, assistant, _ ) | {
709727 acc + * user. char_count ( ) + * assistant. char_count ( )
710728 } )
711729 } )
@@ -730,9 +748,9 @@ pub struct ConversationSize {
730748/// Converts a list of user/assistant message pairs into a flattened list of ChatMessage.
731749fn flatten_history < ' a , T > ( history : T ) -> Vec < ChatMessage >
732750where
733- T : Iterator < Item = & ' a ( UserMessage , AssistantMessage ) > ,
751+ T : Iterator < Item = & ' a HistoryEntry > ,
734752{
735- history. fold ( Vec :: new ( ) , |mut acc, ( user, assistant) | {
753+ history. fold ( Vec :: new ( ) , |mut acc, ( user, assistant, _ ) | {
736754 acc. push ( ChatMessage :: UserInputMessage ( user. clone ( ) . into_history_entry ( ) ) ) ;
737755 acc. push ( ChatMessage :: AssistantResponseMessage ( assistant. clone ( ) . into ( ) ) ) ;
738756 acc
@@ -774,7 +792,7 @@ fn format_hook_context<'a>(hook_results: impl IntoIterator<Item = &'a (Hook, Str
774792}
775793
776794fn enforce_conversation_invariants (
777- history : & mut VecDeque < ( UserMessage , AssistantMessage ) > ,
795+ history : & mut VecDeque < HistoryEntry > ,
778796 next_message : & mut Option < UserMessage > ,
779797 tools : & HashMap < ToolOrigin , Vec < Tool > > ,
780798) -> ( usize , usize ) {
@@ -791,7 +809,7 @@ fn enforce_conversation_invariants(
791809 . iter ( )
792810 . enumerate ( )
793811 . skip ( 1 )
794- . find ( |( _, ( m, _) ) | -> bool { !m. has_tool_use_results ( ) } )
812+ . find ( |( _, ( m, _, _ ) ) | -> bool { !m. has_tool_use_results ( ) } )
795813 . map ( |v| v. 0 )
796814 {
797815 Some ( i) => {
@@ -815,7 +833,7 @@ fn enforce_conversation_invariants(
815833
816834 // If the first message contains tool results, then we add the results to the content field
817835 // instead. This is required to avoid validation errors.
818- if let Some ( ( user, _) ) = history. front_mut ( ) {
836+ if let Some ( ( user, _, _ ) ) = history. front_mut ( ) {
819837 if user. has_tool_use_results ( ) {
820838 user. replace_content_with_tool_use_results ( ) ;
821839 }
@@ -828,7 +846,7 @@ fn enforce_conversation_invariants(
828846 history. range ( valid_history_range. 0 ..valid_history_range. 1 ) . last ( ) ,
829847 ) {
830848 ( Some ( next_message) , prev_msg) if next_message. has_tool_use_results ( ) => match prev_msg {
831- None | Some ( ( _, AssistantMessage :: Response { .. } ) ) => {
849+ None | Some ( ( _, AssistantMessage :: Response { .. } , _ ) ) => {
832850 next_message. replace_content_with_tool_use_results ( ) ;
833851 } ,
834852 _ => ( ) ,
@@ -838,7 +856,7 @@ fn enforce_conversation_invariants(
838856
839857 // If the last message from the assistant contains tool uses AND next_message is set, we need to
840858 // ensure that next_message contains tool results.
841- if let ( Some ( ( _, AssistantMessage :: ToolUse { tool_uses, .. } ) ) , Some ( user_msg) ) = (
859+ if let ( Some ( ( _, AssistantMessage :: ToolUse { tool_uses, .. } , _ ) ) , Some ( user_msg) ) = (
842860 history. range ( valid_history_range. 0 ..valid_history_range. 1 ) . last ( ) ,
843861 next_message,
844862 ) {
@@ -859,7 +877,7 @@ fn enforce_conversation_invariants(
859877}
860878
861879fn enforce_tool_use_history_invariants (
862- history : & mut VecDeque < ( UserMessage , AssistantMessage ) > ,
880+ history : & mut VecDeque < HistoryEntry > ,
863881 tools : & HashMap < ToolOrigin , Vec < Tool > > ,
864882) {
865883 let tool_names: HashSet < _ > = tools
@@ -872,7 +890,7 @@ fn enforce_tool_use_history_invariants(
872890 . filter ( |name| * name != DUMMY_TOOL_NAME )
873891 . collect ( ) ;
874892
875- for ( _, assistant) in history {
893+ for ( _, assistant, _ ) in history {
876894 if let AssistantMessage :: ToolUse { tool_uses, .. } = assistant {
877895 for tool_use in tool_uses {
878896 if tool_names. contains ( tool_use. name . as_str ( ) ) {
@@ -1028,7 +1046,7 @@ mod tests {
10281046 . await
10291047 . unwrap ( ) ;
10301048 assert_conversation_state_invariants ( s, i) ;
1031- conversation. push_assistant_message ( & mut os, AssistantMessage :: new_response ( None , i. to_string ( ) ) ) ;
1049+ conversation. push_assistant_message ( & mut os, AssistantMessage :: new_response ( None , i. to_string ( ) ) , None ) ;
10321050 conversation. set_next_user_message ( i. to_string ( ) ) . await ;
10331051 }
10341052 }
@@ -1065,6 +1083,7 @@ mod tests {
10651083 args: serde_json:: Value :: Null ,
10661084 ..Default :: default ( )
10671085 } ] ) ,
1086+ None ,
10681087 ) ;
10691088 conversation. add_tool_results ( vec ! [ ToolUseResult {
10701089 tool_use_id: "tool_id" . to_string( ) ,
@@ -1099,14 +1118,15 @@ mod tests {
10991118 args: serde_json:: Value :: Null ,
11001119 ..Default :: default ( )
11011120 } ] ) ,
1121+ None ,
11021122 ) ;
11031123 conversation. add_tool_results ( vec ! [ ToolUseResult {
11041124 tool_use_id: "tool_id" . to_string( ) ,
11051125 content: vec![ ] ,
11061126 status: ToolResultStatus :: Success ,
11071127 } ] ) ;
11081128 } else {
1109- conversation. push_assistant_message ( & mut os, AssistantMessage :: new_response ( None , i. to_string ( ) ) ) ;
1129+ conversation. push_assistant_message ( & mut os, AssistantMessage :: new_response ( None , i. to_string ( ) ) , None ) ;
11101130 conversation. set_next_user_message ( i. to_string ( ) ) . await ;
11111131 }
11121132 }
@@ -1147,7 +1167,7 @@ mod tests {
11471167
11481168 assert_conversation_state_invariants ( s, i) ;
11491169
1150- conversation. push_assistant_message ( & mut os, AssistantMessage :: new_response ( None , i. to_string ( ) ) ) ;
1170+ conversation. push_assistant_message ( & mut os, AssistantMessage :: new_response ( None , i. to_string ( ) ) , None ) ;
11511171 conversation. set_next_user_message ( i. to_string ( ) ) . await ;
11521172 }
11531173 }
@@ -1206,7 +1226,7 @@ mod tests {
12061226 s. user_input_message. content
12071227 ) ;
12081228
1209- conversation. push_assistant_message ( & mut os, AssistantMessage :: new_response ( None , i. to_string ( ) ) ) ;
1229+ conversation. push_assistant_message ( & mut os, AssistantMessage :: new_response ( None , i. to_string ( ) ) , None ) ;
12101230 conversation. set_next_user_message ( i. to_string ( ) ) . await ;
12111231 }
12121232 }
0 commit comments