@@ -152,6 +152,11 @@ pub struct RunCli {
152152 /// Timeout in seconds (0 for no timeout).
153153 #[ arg( long = "timeout" , default_value_t = 0 ) ]
154154 pub timeout : u64 ,
155+
156+ /// Preview what would be sent without executing.
157+ /// Shows estimated token counts including system prompt and tool definitions.
158+ #[ arg( long = "dry-run" ) ]
159+ pub dry_run : bool ,
155160}
156161
157162/// Tool display information for formatted output.
@@ -467,6 +472,11 @@ impl RunCli {
467472 attachments : & [ FileAttachment ] ,
468473 session_mode : SessionMode ,
469474 ) -> Result < ( ) > {
475+ // Handle dry-run mode - show token estimates without executing
476+ if self . dry_run {
477+ return self . run_dry_run ( message, attachments) . await ;
478+ }
479+
470480 let is_json = matches ! ( self . format, OutputFormat :: Json | OutputFormat :: Jsonl ) ;
471481 let is_terminal = io:: stdout ( ) . is_terminal ( ) ;
472482
@@ -828,6 +838,109 @@ impl RunCli {
828838
829839 Ok ( ( ) )
830840 }
841+
842+ /// Run in dry-run mode - show token estimates without executing.
843+ async fn run_dry_run ( & self , message : & str , attachments : & [ FileAttachment ] ) -> Result < ( ) > {
844+ use cortex_engine:: tokenizer:: { TokenCounter , TokenizerType } ;
845+
846+ let config = cortex_engine:: Config :: default ( ) ;
847+ let model = self
848+ . model
849+ . as_ref ( )
850+ . map ( |m| resolve_model_alias ( m) . to_string ( ) )
851+ . unwrap_or_else ( || config. model . clone ( ) ) ;
852+
853+ let mut counter = TokenCounter :: for_model ( & model) ;
854+
855+ // Count user prompt tokens
856+ let user_prompt_tokens = counter. count ( message) ;
857+
858+ // Count attachment tokens
859+ let mut attachment_tokens = 0u32 ;
860+ for attachment in attachments {
861+ let content =
862+ std:: fs:: read_to_string ( & attachment. path ) . unwrap_or_else ( |_| String :: new ( ) ) ;
863+ attachment_tokens += counter. count ( & content) ;
864+ // Add overhead for file markers
865+ attachment_tokens += 20 ; // Approximate overhead for "--- File: ... ---" markers
866+ }
867+
868+ // Estimate system prompt tokens (typical system prompt is ~500-2000 tokens)
869+ // This is an approximation as the actual system prompt varies
870+ let system_prompt_tokens = 1500u32 ;
871+
872+ // Estimate tool definition tokens
873+ // Each tool definition is approximately 100-200 tokens on average
874+ // Common tools: Execute, Read, Write, Edit, LS, Grep, Glob, etc.
875+ let tool_count = 15 ; // Approximate number of default tools
876+ let tool_tokens = tool_count * 150 ; // ~150 tokens per tool definition
877+
878+ // Calculate totals
879+ let total_input_tokens =
880+ user_prompt_tokens + attachment_tokens + system_prompt_tokens + tool_tokens;
881+
882+ // Output based on format
883+ if matches ! ( self . format, OutputFormat :: Json | OutputFormat :: Jsonl ) {
884+ let output = serde_json:: json!( {
885+ "dry_run" : true ,
886+ "model" : model,
887+ "token_estimates" : {
888+ "user_prompt" : user_prompt_tokens,
889+ "attachments" : attachment_tokens,
890+ "system_prompt" : system_prompt_tokens,
891+ "tool_definitions" : tool_tokens,
892+ "total_input" : total_input_tokens,
893+ } ,
894+ "message_preview" : if message. len( ) > 100 {
895+ format!( "{}..." , & message[ ..100 ] )
896+ } else {
897+ message. to_string( )
898+ } ,
899+ "attachment_count" : attachments. len( ) ,
900+ } ) ;
901+ println ! ( "{}" , serde_json:: to_string_pretty( & output) ?) ;
902+ } else {
903+ println ! ( "Dry Run - Token Estimate" ) ;
904+ println ! ( "{}" , "=" . repeat( 50 ) ) ;
905+ println ! ( ) ;
906+ println ! ( "Model: {}" , model) ;
907+ println ! ( ) ;
908+ println ! ( "Token Breakdown:" ) ;
909+ println ! ( " User prompt: {:>8} tokens" , user_prompt_tokens) ;
910+ if !attachments. is_empty ( ) {
911+ println ! (
912+ " Attachments: {:>8} tokens ({} files)" ,
913+ attachment_tokens,
914+ attachments. len( )
915+ ) ;
916+ }
917+ println ! (
918+ " System prompt: {:>8} tokens (estimated)" ,
919+ system_prompt_tokens
920+ ) ;
921+ println ! (
922+ " Tool definitions: {:>8} tokens (estimated, {} tools)" ,
923+ tool_tokens, tool_count
924+ ) ;
925+ println ! ( " {}" , "-" . repeat( 30 ) ) ;
926+ println ! ( " Total input: {:>8} tokens" , total_input_tokens) ;
927+ println ! ( ) ;
928+ println ! ( "Note: System prompt and tool definition token counts are estimates." ) ;
929+ println ! ( "Actual counts may vary based on agent configuration." ) ;
930+ if !message. is_empty ( ) {
931+ println ! ( ) ;
932+ println ! ( "Message preview:" ) ;
933+ let preview = if message. len ( ) > 200 {
934+ format ! ( " {}..." , & message[ ..200 ] )
935+ } else {
936+ format ! ( " {}" , message)
937+ } ;
938+ println ! ( "{}" , preview) ;
939+ }
940+ }
941+
942+ Ok ( ( ) )
943+ }
831944}
832945
833946/// Session handling mode.
0 commit comments