@@ -24,6 +24,7 @@ use codex_core::ConversationManager;
2424use codex_core:: config:: Config ;
2525use codex_core:: config:: edit:: ConfigEditsBuilder ;
2626use codex_core:: model_family:: find_family_for_model;
27+ use codex_core:: protocol:: FinalOutput ;
2728use codex_core:: protocol:: SessionSource ;
2829use codex_core:: protocol:: TokenUsage ;
2930use codex_core:: protocol_config_types:: ReasoningEffort as ReasoningEffortConfig ;
@@ -54,6 +55,29 @@ pub struct AppExitInfo {
5455 pub update_action : Option < UpdateAction > ,
5556}
5657
58+ fn session_summary (
59+ token_usage : TokenUsage ,
60+ conversation_id : Option < ConversationId > ,
61+ ) -> Option < SessionSummary > {
62+ if token_usage. is_zero ( ) {
63+ return None ;
64+ }
65+
66+ let usage_line = FinalOutput :: from ( token_usage) . to_string ( ) ;
67+ let resume_command =
68+ conversation_id. map ( |conversation_id| format ! ( "codex resume {conversation_id}" ) ) ;
69+ Some ( SessionSummary {
70+ usage_line,
71+ resume_command,
72+ } )
73+ }
74+
75+ #[ derive( Debug , Clone , PartialEq , Eq ) ]
76+ struct SessionSummary {
77+ usage_line : String ,
78+ resume_command : Option < String > ,
79+ }
80+
5781fn should_show_model_migration_prompt (
5882 current_model : & str ,
5983 target_model : & str ,
@@ -365,6 +389,10 @@ impl App {
365389 async fn handle_event ( & mut self , tui : & mut tui:: Tui , event : AppEvent ) -> Result < bool > {
366390 match event {
367391 AppEvent :: NewSession => {
392+ let summary = session_summary (
393+ self . chat_widget . token_usage ( ) ,
394+ self . chat_widget . conversation_id ( ) ,
395+ ) ;
368396 let init = crate :: chatwidget:: ChatWidgetInit {
369397 config : self . config . clone ( ) ,
370398 frame_requester : tui. frame_requester ( ) ,
@@ -376,6 +404,14 @@ impl App {
376404 feedback : self . feedback . clone ( ) ,
377405 } ;
378406 self . chat_widget = ChatWidget :: new ( init, self . server . clone ( ) ) ;
407+ if let Some ( summary) = summary {
408+ let mut lines: Vec < Line < ' static > > = vec ! [ summary. usage_line. clone( ) . into( ) ] ;
409+ if let Some ( command) = summary. resume_command {
410+ let spans = vec ! [ "To continue this session, run " . into( ) , command. cyan( ) ] ;
411+ lines. push ( spans. into ( ) ) ;
412+ }
413+ self . chat_widget . add_plain_history_lines ( lines) ;
414+ }
379415 tui. frame_requester ( ) . schedule_frame ( ) ;
380416 }
381417 AppEvent :: InsertHistoryCell ( cell) => {
@@ -970,4 +1006,31 @@ mod tests {
9701006 assert_eq ! ( nth, 1 ) ;
9711007 assert_eq ! ( prefill, "follow-up (edited)" ) ;
9721008 }
1009+
1010+ #[ test]
1011+ fn session_summary_skip_zero_usage ( ) {
1012+ assert ! ( session_summary( TokenUsage :: default ( ) , None ) . is_none( ) ) ;
1013+ }
1014+
1015+ #[ test]
1016+ fn session_summary_includes_resume_hint ( ) {
1017+ let usage = TokenUsage {
1018+ input_tokens : 10 ,
1019+ output_tokens : 2 ,
1020+ total_tokens : 12 ,
1021+ ..Default :: default ( )
1022+ } ;
1023+ let conversation =
1024+ ConversationId :: from_string ( "123e4567-e89b-12d3-a456-426614174000" ) . unwrap ( ) ;
1025+
1026+ let summary = session_summary ( usage, Some ( conversation) ) . expect ( "summary" ) ;
1027+ assert_eq ! (
1028+ summary. usage_line,
1029+ "Token usage: total=12 input=10 output=2"
1030+ ) ;
1031+ assert_eq ! (
1032+ summary. resume_command,
1033+ Some ( "codex resume 123e4567-e89b-12d3-a456-426614174000" . to_string( ) )
1034+ ) ;
1035+ }
9731036}
0 commit comments