@@ -54,7 +54,7 @@ pub struct UserMessage {
54
54
pub additional_context : String ,
55
55
pub env_context : UserEnvContext ,
56
56
pub content : UserMessageContent ,
57
- pub timestamp : DateTime < Utc > ,
57
+ pub timestamp : Option < DateTime < Utc > > ,
58
58
pub images : Option < Vec < ImageBlock > > ,
59
59
}
60
60
@@ -107,20 +107,24 @@ impl UserMessageContent {
107
107
impl UserMessage {
108
108
/// Creates a new [UserMessage::Prompt], automatically detecting and adding the user's
109
109
/// environment [UserEnvContext].
110
- pub fn new_prompt ( prompt : String ) -> Self {
110
+ pub fn new_prompt ( prompt : String , timestamp : Option < DateTime < Utc > > ) -> Self {
111
111
Self {
112
112
images : None ,
113
- timestamp : Utc :: now ( ) ,
113
+ timestamp,
114
114
additional_context : String :: new ( ) ,
115
115
env_context : UserEnvContext :: generate_new ( ) ,
116
116
content : UserMessageContent :: Prompt { prompt } ,
117
117
}
118
118
}
119
119
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 {
121
125
Self {
122
126
images : None ,
123
- timestamp : Utc :: now ( ) ,
127
+ timestamp,
124
128
additional_context : String :: new ( ) ,
125
129
env_context : UserEnvContext :: generate_new ( ) ,
126
130
content : UserMessageContent :: CancelledToolUses {
@@ -141,7 +145,7 @@ impl UserMessage {
141
145
pub fn new_tool_use_results ( results : Vec < ToolUseResult > ) -> Self {
142
146
Self {
143
147
additional_context : String :: new ( ) ,
144
- timestamp : Utc :: now ( ) ,
148
+ timestamp : None ,
145
149
env_context : UserEnvContext :: generate_new ( ) ,
146
150
content : UserMessageContent :: ToolUseResults {
147
151
tool_use_results : results,
@@ -150,10 +154,14 @@ impl UserMessage {
150
154
}
151
155
}
152
156
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 {
154
162
Self {
155
163
additional_context : String :: new ( ) ,
156
- timestamp : Utc :: now ( ) ,
164
+ timestamp,
157
165
env_context : UserEnvContext :: generate_new ( ) ,
158
166
content : UserMessageContent :: ToolUseResults {
159
167
tool_use_results : results,
@@ -278,31 +286,37 @@ impl UserMessage {
278
286
}
279
287
}
280
288
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].
282
291
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 ( ) ;
285
293
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 " ,
289
297
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 ) ,
291
300
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' ) ;
303
307
}
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 ( )
306
320
}
307
321
}
308
322
@@ -549,20 +563,44 @@ mod tests {
549
563
550
564
#[ test]
551
565
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 ) ;
553
591
554
592
let msgs = [
555
593
msg. clone ( ) . into_user_input_message ( None , & HashMap :: new ( ) ) ,
556
594
msg. clone ( ) . into_history_entry ( ) ,
557
595
] ;
558
596
559
597
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 ( ) ) ) ;
566
604
}
567
605
}
568
606
}
0 commit comments