11use std:: sync:: Arc ;
22use std:: sync:: atomic:: AtomicI64 ;
3+ use std:: sync:: atomic:: AtomicU64 ;
34use std:: sync:: atomic:: Ordering ;
5+ use std:: time:: Duration ;
46
57use anyhow:: Result ;
68use anyhow:: anyhow;
79use chrono:: DateTime ;
810use chrono:: Utc ;
11+ use codex_core:: SleepFuture ;
912use codex_core:: TimeFuture ;
1013use codex_core:: TimeProvider ;
1114use codex_core:: config:: CurrentTimeReminderConfig ;
@@ -40,22 +43,34 @@ const SECOND_REMINDER: &str = "It is 2026-06-17 17:35:15 UTC.";
4043const THIRD_REMINDER : & str = "It is 2026-06-17 17:36:15 UTC." ;
4144const FIRST_TIME_UNIX_SECONDS : i64 = 1_781_717_655 ;
4245
43- struct TestTimeProvider ( AtomicI64 ) ;
46+ struct TestTimeProvider {
47+ current_time : AtomicI64 ,
48+ sleep_seconds : AtomicU64 ,
49+ }
4450
4551impl Default for TestTimeProvider {
4652 fn default ( ) -> Self {
47- Self ( AtomicI64 :: new ( FIRST_TIME_UNIX_SECONDS ) )
53+ Self {
54+ current_time : AtomicI64 :: new ( FIRST_TIME_UNIX_SECONDS ) ,
55+ sleep_seconds : AtomicU64 :: new ( 0 ) ,
56+ }
4857 }
4958}
5059
5160impl TimeProvider for TestTimeProvider {
5261 fn current_time ( & self , _thread_id : ThreadId ) -> TimeFuture < ' _ > {
53- let timestamp = self . 0 . fetch_add ( 60 , Ordering :: Relaxed ) ;
62+ let timestamp = self . current_time . fetch_add ( 60 , Ordering :: Relaxed ) ;
5463 Box :: pin ( async move {
5564 Ok ( DateTime :: < Utc > :: from_timestamp ( timestamp, 0 )
5665 . expect ( "test timestamp should be valid" ) )
5766 } )
5867 }
68+
69+ fn sleep ( & self , _thread_id : ThreadId , duration : Duration ) -> SleepFuture < ' _ > {
70+ self . sleep_seconds
71+ . store ( duration. as_secs ( ) , Ordering :: Relaxed ) ;
72+ Box :: pin ( async { Ok ( ( ) ) } )
73+ }
5974}
6075
6176struct FailingTimeProvider ;
@@ -64,6 +79,10 @@ impl TimeProvider for FailingTimeProvider {
6479 fn current_time ( & self , _thread_id : ThreadId ) -> TimeFuture < ' _ > {
6580 Box :: pin ( async { Err ( anyhow ! ( "test clock unavailable" ) ) } )
6681 }
82+
83+ fn sleep ( & self , _thread_id : ThreadId , _duration : Duration ) -> SleepFuture < ' _ > {
84+ Box :: pin ( async { Err ( anyhow ! ( "test clock unavailable" ) ) } )
85+ }
6786}
6887
6988fn current_time_reminders ( request : & ResponsesRequest ) -> Vec < String > {
@@ -325,3 +344,67 @@ async fn current_time_tool_returns_the_latest_time() -> Result<()> {
325344
326345 Ok ( ( ) )
327346}
347+
348+ #[ tokio:: test( flavor = "multi_thread" , worker_threads = 2 ) ]
349+ async fn sleep_tool_uses_configured_time_provider ( ) -> Result < ( ) > {
350+ skip_if_no_network ! ( Ok ( ( ) ) ) ;
351+
352+ const CALL_ID : & str = "sleep" ;
353+ const DURATION_MS : u64 = 12 * 60 * 60 * 1000 ;
354+
355+ let server = start_mock_server ( ) . await ;
356+ let responses = mount_sse_sequence (
357+ & server,
358+ vec ! [
359+ sse( vec![
360+ ev_response_created( "resp-1" ) ,
361+ ev_function_call_with_namespace(
362+ CALL_ID ,
363+ "clock" ,
364+ "sleep" ,
365+ & json!( { "duration_ms" : DURATION_MS } ) . to_string( ) ,
366+ ) ,
367+ ev_completed( "resp-1" ) ,
368+ ] ) ,
369+ sse( vec![
370+ ev_response_created( "resp-2" ) ,
371+ ev_assistant_message( "msg-2" , "done" ) ,
372+ ev_completed( "resp-2" ) ,
373+ ] ) ,
374+ ] ,
375+ )
376+ . await ;
377+ let time_provider = Arc :: new ( TestTimeProvider :: default ( ) ) ;
378+ let test = test_codex ( )
379+ . with_config ( |config| {
380+ enable_current_time_reminder (
381+ config,
382+ /*interval*/ 3_000 ,
383+ CurrentTimeSource :: External ,
384+ ) ;
385+ config
386+ . current_time_reminder
387+ . as_mut ( )
388+ . expect ( "current-time reminder config should be present" )
389+ . sleep_tool = true ;
390+ } )
391+ . with_external_time_provider ( time_provider. clone ( ) )
392+ . build ( & server)
393+ . await ?;
394+
395+ test. submit_turn ( "sleep" ) . await ?;
396+
397+ assert_eq ! (
398+ time_provider. sleep_seconds. load( Ordering :: Relaxed ) ,
399+ DURATION_MS / 1_000
400+ ) ;
401+ let requests = responses. requests ( ) ;
402+ assert_eq ! ( requests. len( ) , 2 ) ;
403+ assert ! (
404+ requests[ 1 ]
405+ . function_call_output_text( CALL_ID )
406+ . is_some_and( |output| output. ends_with( "Sleep completed." ) )
407+ ) ;
408+
409+ Ok ( ( ) )
410+ }
0 commit comments