1
1
use std:: collections:: HashMap ;
2
2
use std:: env;
3
3
4
+ use chrono:: {
5
+ DateTime ,
6
+ Utc ,
7
+ } ;
4
8
use serde:: {
5
9
Deserialize ,
6
10
Serialize ,
@@ -14,6 +18,10 @@ use super::consts::{
14
18
MAX_CURRENT_WORKING_DIRECTORY_LEN ,
15
19
MAX_USER_MESSAGE_SIZE ,
16
20
} ;
21
+ use super :: conversation:: {
22
+ CONTEXT_ENTRY_END_HEADER ,
23
+ CONTEXT_ENTRY_START_HEADER ,
24
+ } ;
17
25
use super :: tools:: {
18
26
InvokeOutput ,
19
27
OutputKind ,
@@ -46,6 +54,7 @@ pub struct UserMessage {
46
54
pub additional_context : String ,
47
55
pub env_context : UserEnvContext ,
48
56
pub content : UserMessageContent ,
57
+ pub timestamp : DateTime < Utc > ,
49
58
pub images : Option < Vec < ImageBlock > > ,
50
59
}
51
60
@@ -101,6 +110,7 @@ impl UserMessage {
101
110
pub fn new_prompt ( prompt : String ) -> Self {
102
111
Self {
103
112
images : None ,
113
+ timestamp : Utc :: now ( ) ,
104
114
additional_context : String :: new ( ) ,
105
115
env_context : UserEnvContext :: generate_new ( ) ,
106
116
content : UserMessageContent :: Prompt { prompt } ,
@@ -110,6 +120,7 @@ impl UserMessage {
110
120
pub fn new_cancelled_tool_uses < ' a > ( prompt : Option < String > , tool_use_ids : impl Iterator < Item = & ' a str > ) -> Self {
111
121
Self {
112
122
images : None ,
123
+ timestamp : Utc :: now ( ) ,
113
124
additional_context : String :: new ( ) ,
114
125
env_context : UserEnvContext :: generate_new ( ) ,
115
126
content : UserMessageContent :: CancelledToolUses {
@@ -130,6 +141,7 @@ impl UserMessage {
130
141
pub fn new_tool_use_results ( results : Vec < ToolUseResult > ) -> Self {
131
142
Self {
132
143
additional_context : String :: new ( ) ,
144
+ timestamp : Utc :: now ( ) ,
133
145
env_context : UserEnvContext :: generate_new ( ) ,
134
146
content : UserMessageContent :: ToolUseResults {
135
147
tool_use_results : results,
@@ -141,6 +153,7 @@ impl UserMessage {
141
153
pub fn new_tool_use_results_with_images ( results : Vec < ToolUseResult > , images : Vec < ImageBlock > ) -> Self {
142
154
Self {
143
155
additional_context : String :: new ( ) ,
156
+ timestamp : Utc :: now ( ) ,
144
157
env_context : UserEnvContext :: generate_new ( ) ,
145
158
content : UserMessageContent :: ToolUseResults {
146
159
tool_use_results : results,
@@ -267,13 +280,25 @@ impl UserMessage {
267
280
268
281
/// Returns a formatted [String] containing [Self::additional_context] and [Self::prompt].
269
282
fn content_with_context ( & self ) -> String {
270
- match ( self . additional_context . is_empty ( ) , self . prompt ( ) ) {
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 ) ;
285
+
286
+ let prompt_with_timestamp = self . prompt ( ) . map ( |p| {
287
+ format ! (
288
+ "{}Current UTC time: {}{}{}{}{}" ,
289
+ CONTEXT_ENTRY_START_HEADER ,
290
+ timestamp,
291
+ 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) {
271
299
// Only add special delimiters if we have both a prompt and additional context
272
- ( false , Some ( prompt) ) => format ! (
273
- "{} {}{}{}" ,
274
- self . additional_context, USER_ENTRY_START_HEADER , prompt, USER_ENTRY_END_HEADER
275
- ) ,
276
- ( true , Some ( prompt) ) => prompt. to_string ( ) ,
300
+ ( false , Some ( prompt) ) => format ! ( "{}\n {}" , self . additional_context, prompt) ,
301
+ ( true , Some ( prompt) ) => prompt,
277
302
_ => self . additional_context . clone ( ) ,
278
303
}
279
304
. trim ( )
@@ -521,4 +546,23 @@ mod tests {
521
546
assert ! ( env_state. operating_system. as_ref( ) . is_some_and( |os| !os. is_empty( ) ) ) ;
522
547
println ! ( "{env_state:?}" ) ;
523
548
}
549
+
550
+ #[ test]
551
+ fn test_user_input_message_timestamp_formatting ( ) {
552
+ let msg = UserMessage :: new_prompt ( "hello world" . to_string ( ) ) ;
553
+
554
+ let msgs = [
555
+ msg. clone ( ) . into_user_input_message ( None , & HashMap :: new ( ) ) ,
556
+ msg. clone ( ) . into_history_entry ( ) ,
557
+ ] ;
558
+
559
+ 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 ) ;
566
+ }
567
+ }
524
568
}
0 commit comments