@@ -54,7 +54,7 @@ pub struct UserMessage {
5454 pub additional_context : String ,
5555 pub env_context : UserEnvContext ,
5656 pub content : UserMessageContent ,
57- pub timestamp : DateTime < Utc > ,
57+ pub timestamp : Option < DateTime < Utc > > ,
5858 pub images : Option < Vec < ImageBlock > > ,
5959}
6060
@@ -107,20 +107,24 @@ impl UserMessageContent {
107107impl UserMessage {
108108 /// Creates a new [UserMessage::Prompt], automatically detecting and adding the user's
109109 /// environment [UserEnvContext].
110- pub fn new_prompt ( prompt : String ) -> Self {
110+ pub fn new_prompt ( prompt : String , timestamp : Option < DateTime < Utc > > ) -> Self {
111111 Self {
112112 images : None ,
113- timestamp : Utc :: now ( ) ,
113+ timestamp,
114114 additional_context : String :: new ( ) ,
115115 env_context : UserEnvContext :: generate_new ( ) ,
116116 content : UserMessageContent :: Prompt { prompt } ,
117117 }
118118 }
119119
120- pub fn new_cancelled_tool_uses < ' a > ( prompt : Option < String > , tool_use_ids : impl Iterator < Item = & ' a str > ) -> Self {
120+ pub fn new_cancelled_tool_uses < ' a > (
121+ prompt : Option < String > ,
122+ tool_use_ids : impl Iterator < Item = & ' a str > ,
123+ timestamp : Option < DateTime < Utc > > ,
124+ ) -> Self {
121125 Self {
122126 images : None ,
123- timestamp : Utc :: now ( ) ,
127+ timestamp,
124128 additional_context : String :: new ( ) ,
125129 env_context : UserEnvContext :: generate_new ( ) ,
126130 content : UserMessageContent :: CancelledToolUses {
@@ -141,7 +145,7 @@ impl UserMessage {
141145 pub fn new_tool_use_results ( results : Vec < ToolUseResult > ) -> Self {
142146 Self {
143147 additional_context : String :: new ( ) ,
144- timestamp : Utc :: now ( ) ,
148+ timestamp : None ,
145149 env_context : UserEnvContext :: generate_new ( ) ,
146150 content : UserMessageContent :: ToolUseResults {
147151 tool_use_results : results,
@@ -150,10 +154,14 @@ impl UserMessage {
150154 }
151155 }
152156
153- pub fn new_tool_use_results_with_images ( results : Vec < ToolUseResult > , images : Vec < ImageBlock > ) -> Self {
157+ pub fn new_tool_use_results_with_images (
158+ results : Vec < ToolUseResult > ,
159+ images : Vec < ImageBlock > ,
160+ timestamp : Option < DateTime < Utc > > ,
161+ ) -> Self {
154162 Self {
155163 additional_context : String :: new ( ) ,
156- timestamp : Utc :: now ( ) ,
164+ timestamp,
157165 env_context : UserEnvContext :: generate_new ( ) ,
158166 content : UserMessageContent :: ToolUseResults {
159167 tool_use_results : results,
@@ -278,31 +286,37 @@ impl UserMessage {
278286 }
279287 }
280288
281- /// Returns a formatted [String] containing [Self::additional_context] and [Self::prompt].
289+ /// Returns a formatted [String] containing [Self::additional_context], [Self::timestamp], and
290+ /// [Self::prompt].
282291 fn content_with_context ( & self ) -> String {
283- // Format the time with iso8601 format using Z, e.g. 2025-08-08T17:43:28.672Z
284- let timestamp = self . timestamp . to_rfc3339_opts ( chrono:: SecondsFormat :: Millis , true ) ;
292+ let mut content = String :: new ( ) ;
285293
286- let prompt_with_timestamp = self . prompt ( ) . map ( |p| {
287- format ! (
288- "{}Current UTC time: {}{}{}{}{} " ,
294+ if let Some ( ts ) = self . timestamp {
295+ content . push_str ( & format ! (
296+ "{}Current UTC time: {}{}\n " ,
289297 CONTEXT_ENTRY_START_HEADER ,
290- timestamp,
298+ // Format the time with iso8601 format using Z, e.g. 2025-08-08T17:43:28.672Z
299+ ts. to_rfc3339_opts( chrono:: SecondsFormat :: Millis , true ) ,
291300 CONTEXT_ENTRY_END_HEADER ,
292- USER_ENTRY_START_HEADER ,
293- p,
294- USER_ENTRY_END_HEADER
295- )
296- } ) ;
297-
298- match ( self . additional_context . is_empty ( ) , prompt_with_timestamp) {
299- // Only add special delimiters if we have both a prompt and additional context
300- ( false , Some ( prompt) ) => format ! ( "{}\n {}" , self . additional_context, prompt) ,
301- ( true , Some ( prompt) ) => prompt,
302- _ => self . additional_context . clone ( ) ,
301+ ) ) ;
302+ }
303+
304+ if !self . additional_context . is_empty ( ) {
305+ content. push_str ( & self . additional_context ) ;
306+ content. push ( '\n' ) ;
303307 }
304- . trim ( )
305- . to_string ( )
308+
309+ // Only add special delimiters around the user's prompt if there is no timestamp or
310+ // additional context to add.
311+ match ( content. is_empty ( ) , self . prompt ( ) ) {
312+ ( false , Some ( p) ) => {
313+ content. push_str ( & format ! ( "{}{}{}" , USER_ENTRY_START_HEADER , p, USER_ENTRY_END_HEADER ) ) ;
314+ } ,
315+ ( true , Some ( p) ) => content. push_str ( p) ,
316+ _ => ( ) ,
317+ } ;
318+
319+ content. trim ( ) . to_string ( )
306320 }
307321}
308322
@@ -549,20 +563,44 @@ mod tests {
549563
550564 #[ test]
551565 fn test_user_input_message_timestamp_formatting ( ) {
552- let msg = UserMessage :: new_prompt ( "hello world" . to_string ( ) ) ;
566+ const USER_PROMPT : & str = "hello world" ;
567+
568+ let msg = UserMessage :: new_prompt ( USER_PROMPT . to_string ( ) , Some ( Utc :: now ( ) ) ) ;
569+
570+ let msgs = [
571+ msg. clone ( ) . into_user_input_message ( None , & HashMap :: new ( ) ) ,
572+ msg. clone ( ) . into_history_entry ( ) ,
573+ ] ;
574+
575+ for m in msgs {
576+ println ! ( "checking {:?}" , m) ;
577+ assert ! ( m. content. contains( CONTEXT_ENTRY_START_HEADER ) ) ;
578+ assert ! ( m. content. contains( "Current UTC time" ) ) ;
579+ assert ! ( m. content. contains( CONTEXT_ENTRY_END_HEADER ) ) ;
580+ assert ! ( m. content. contains( USER_ENTRY_START_HEADER ) ) ;
581+ assert ! ( m. content. contains( USER_PROMPT ) ) ;
582+ assert ! ( m. content. contains( USER_ENTRY_END_HEADER . trim( ) ) ) ;
583+ }
584+ }
585+
586+ #[ test]
587+ fn test_user_input_message_without_context ( ) {
588+ const USER_PROMPT : & str = "hello world" ;
589+
590+ let msg = UserMessage :: new_prompt ( USER_PROMPT . to_string ( ) , None ) ;
553591
554592 let msgs = [
555593 msg. clone ( ) . into_user_input_message ( None , & HashMap :: new ( ) ) ,
556594 msg. clone ( ) . into_history_entry ( ) ,
557595 ] ;
558596
559597 for m in msgs {
560- m. content . contains ( CONTEXT_ENTRY_START_HEADER ) ;
561- m. content . contains ( "Current UTC time" ) ;
562- m. content . contains ( CONTEXT_ENTRY_END_HEADER ) ;
563- m. content . contains ( USER_ENTRY_START_HEADER ) ;
564- m. content . contains ( "hello world" ) ;
565- m. content . contains ( USER_ENTRY_END_HEADER ) ;
598+ assert ! ( ! m. content. contains( CONTEXT_ENTRY_START_HEADER ) ) ;
599+ assert ! ( ! m. content. contains( "Current UTC time" ) ) ;
600+ assert ! ( ! m. content. contains( CONTEXT_ENTRY_END_HEADER ) ) ;
601+ assert ! ( ! m. content. contains( USER_ENTRY_START_HEADER ) ) ;
602+ assert ! ( m. content. contains( USER_PROMPT ) ) ;
603+ assert ! ( ! m. content. contains( USER_ENTRY_END_HEADER . trim ( ) ) ) ;
566604 }
567605 }
568606}
0 commit comments