@@ -8,6 +8,7 @@ use eyre::{
88 bail,
99} ;
1010use fig_api_client:: model:: {
11+ AssistantResponseMessage ,
1112 ChatMessage ,
1213 ConversationState as FigConversationState ,
1314 EnvState ,
@@ -28,9 +29,14 @@ use fig_settings::history::{
2829 OrderBy ,
2930} ;
3031use fig_util:: Shell ;
32+ use rand:: distributions:: {
33+ Alphanumeric ,
34+ DistString ,
35+ } ;
3136use regex:: Regex ;
3237use tracing:: {
3338 error,
39+ info,
3440 warn,
3541} ;
3642
@@ -66,18 +72,21 @@ const MAX_CONVERSATION_STATE_HISTORY_LEN: usize = 25;
6672/// Tracks state related to an ongoing conversation.
6773#[ derive( Debug , Clone ) ]
6874pub struct ConversationState {
69- pub conversation_id : Option < String > ,
75+ /// Randomly generated on creation.
76+ conversation_id : String ,
7077 /// The next user message to be sent as part of the conversation. Required to be [Some] before
7178 /// calling [Self::as_sendable_conversation_state].
72- pub next_message : Option < Message > ,
73- pub history : VecDeque < Message > ,
79+ pub next_message : Option < UserInputMessage > ,
80+ pub history : VecDeque < ChatMessage > ,
7481 tools : Vec < Tool > ,
7582}
7683
7784impl ConversationState {
7885 pub fn new ( tool_config : ToolConfiguration ) -> Self {
86+ let conversation_id = Alphanumeric . sample_string ( & mut rand:: thread_rng ( ) , 9 ) ;
87+ info ! ( ?conversation_id, "Generated new conversation id" ) ;
7988 Self {
80- conversation_id : None ,
89+ conversation_id,
8190 next_message : None ,
8291 history : VecDeque :: new ( ) ,
8392 tools : tool_config
@@ -126,30 +135,32 @@ impl ConversationState {
126135 }
127136 }
128137
129- let msg = Message ( ChatMessage :: UserInputMessage ( UserInputMessage {
138+ let msg = UserInputMessage {
130139 content : input,
131140 user_input_message_context : Some ( user_input_message_context) ,
132141 user_intent : None ,
133- } ) ) ;
142+ } ;
134143 self . next_message = Some ( msg) ;
135144 }
136145
137- pub fn push_assistant_message ( & mut self , message : Message ) {
146+ pub fn push_assistant_message ( & mut self , message : AssistantResponseMessage ) {
138147 debug_assert ! ( self . next_message. is_none( ) , "next_message should not exist" ) ;
139148 if let Some ( next_message) = self . next_message . as_ref ( ) {
140149 warn ! ( ?next_message, "next_message should not exist" ) ;
141150 }
142- self . history . push_back ( message) ;
151+ self . history . push_back ( ChatMessage :: AssistantResponseMessage ( message) ) ;
143152 }
144153
145- /// Returns the conversation id, if available .
146- pub fn conversation_id ( & self ) -> Option < & str > {
147- self . conversation_id . as_deref ( )
154+ /// Returns the conversation id.
155+ pub fn conversation_id ( & self ) -> & str {
156+ self . conversation_id . as_ref ( )
148157 }
149158
150159 /// Returns the message id associated with the last assistant message, if present.
160+ ///
161+ /// This is equivalent to `utterance_id` in the Q API.
151162 pub fn message_id ( & self ) -> Option < & str > {
152- self . history . iter ( ) . last ( ) . and_then ( |m| match & m. 0 {
163+ self . history . iter ( ) . last ( ) . and_then ( |m| match & m {
153164 ChatMessage :: AssistantResponseMessage ( m) => m. message_id . as_deref ( ) ,
154165 ChatMessage :: UserInputMessage ( _) => None ,
155166 } )
@@ -167,11 +178,11 @@ impl ConversationState {
167178 } ,
168179 ..Default :: default ( )
169180 } ;
170- let msg = Message ( ChatMessage :: UserInputMessage ( UserInputMessage {
181+ let msg = UserInputMessage {
171182 content : String :: new ( ) ,
172183 user_input_message_context : Some ( user_input_message_context) ,
173184 user_intent : None ,
174- } ) ) ;
185+ } ;
175186 self . next_message = Some ( msg) ;
176187 }
177188
@@ -197,11 +208,11 @@ impl ConversationState {
197208 } ,
198209 ..Default :: default ( )
199210 } ;
200- let msg = Message ( ChatMessage :: UserInputMessage ( UserInputMessage {
211+ let msg = UserInputMessage {
201212 content : deny_input,
202213 user_input_message_context : Some ( user_input_message_context) ,
203214 user_intent : None ,
204- } ) ) ;
215+ } ;
205216 self . next_message = Some ( msg) ;
206217 }
207218
@@ -219,33 +230,20 @@ impl ConversationState {
219230
220231 // Updating `self` so that the current next_message is moved to history.
221232 let mut last_message = self . next_message . take ( ) . unwrap ( ) ;
222- match & mut last_message. 0 {
223- ChatMessage :: UserInputMessage ( msg) => {
224- if let Some ( ctx) = & mut msg. user_input_message_context {
225- ctx. tools . take ( ) ;
226- }
227- } ,
228- ChatMessage :: AssistantResponseMessage ( _) => ( ) ,
233+ if let Some ( ctx) = & mut last_message. user_input_message_context {
234+ // Don't include the tool spec in all user messages in the history.
235+ ctx. tools . take ( ) ;
229236 }
230- self . history . push_back ( last_message) ;
237+ self . history . push_back ( ChatMessage :: UserInputMessage ( last_message) ) ;
231238
232239 FigConversationState {
233- conversation_id : curr_state. conversation_id ,
234- user_input_message : curr_state
235- . next_message
236- . and_then ( |m| match m. 0 {
237- ChatMessage :: AssistantResponseMessage ( _) => None ,
238- ChatMessage :: UserInputMessage ( user_input_message) => Some ( user_input_message) ,
239- } )
240- . expect ( "no user input message available" ) ,
241- history : Some ( curr_state. history . into_iter ( ) . map ( |m| m. 0 ) . collect ( ) ) ,
240+ conversation_id : Some ( curr_state. conversation_id ) ,
241+ user_input_message : curr_state. next_message . expect ( "no user input message available" ) ,
242+ history : Some ( curr_state. history . into ( ) ) ,
242243 }
243244 }
244245}
245246
246- #[ derive( Debug , Clone ) ]
247- pub struct Message ( pub ChatMessage ) ;
248-
249247impl From < InvokeOutput > for ToolResultContentBlock {
250248 fn from ( value : InvokeOutput ) -> Self {
251249 match value. output {
@@ -554,16 +552,16 @@ mod tests {
554552 async fn test_conversation_state_history_handling ( ) {
555553 let mut conversation_state = ConversationState :: new ( load_tools ( ) . unwrap ( ) ) ;
556554
555+ // First, build a large conversation history. We need to ensure that the order is always
556+ // User -> Assistant -> User -> Assistant ...and so on.
557557 conversation_state. append_new_user_message ( "start" . to_string ( ) ) . await ;
558- for i in 0 ..=20 {
558+ for i in 0 ..=100 {
559559 let _ = conversation_state. as_sendable_conversation_state ( ) ;
560- conversation_state. push_assistant_message ( Message ( ChatMessage :: AssistantResponseMessage (
561- AssistantResponseMessage {
562- message_id : None ,
563- content : i. to_string ( ) ,
564- tool_uses : None ,
565- } ,
566- ) ) ) ;
560+ conversation_state. push_assistant_message ( AssistantResponseMessage {
561+ message_id : None ,
562+ content : i. to_string ( ) ,
563+ tool_uses : None ,
564+ } ) ;
567565 conversation_state. append_new_user_message ( i. to_string ( ) ) . await ;
568566 }
569567
@@ -577,7 +575,7 @@ mod tests {
577575 let last_msg = s. history . as_ref ( ) . unwrap ( ) . iter ( ) . last ( ) . unwrap ( ) ;
578576 match last_msg {
579577 ChatMessage :: AssistantResponseMessage ( assistant_response_message) => {
580- assert_eq ! ( assistant_response_message. content, "20 " ) ;
578+ assert_eq ! ( assistant_response_message. content, "100 " ) ;
581579 } ,
582580 other @ ChatMessage :: UserInputMessage ( _) => {
583581 panic ! ( "Last message should be from the assistant, instead found {:?}" , other)
0 commit comments