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