From 4bd5a6381e31afcb775edde516aff77476e1902c Mon Sep 17 00:00:00 2001 From: clawdbot Date: Mon, 12 Oct 2026 23:57:07 +0000 Subject: [PATCH] feat: implement /status, /cost, and /version slash commands - Add real implementations for /status, /cost, and /version slash commands - /status shows session metadata including message count and token usage - /cost displays cumulative token usage including cache metrics - /version shows the claw-code version - All three commands are fully backed by existing Session infrastructure - Add comprehensive tests for all three commands - Remove expectations that these commands return None These commands fulfill the requirement to implement 2-3 missing GJC slash commands that the existing claw-code runtime can fully support. --- rust/crates/commands/src/lib.rs | 149 ++++++++++++++++++++++++++++++-- 1 file changed, 142 insertions(+), 7 deletions(-) diff --git a/rust/crates/commands/src/lib.rs b/rust/crates/commands/src/lib.rs index a1e70069..2c36a63e 100644 --- a/rust/crates/commands/src/lib.rs +++ b/rust/crates/commands/src/lib.rs @@ -5297,6 +5297,60 @@ fn mcp_server_json(name: &str, server: &ScopedMcpServerConfig) -> Value { }) } +fn render_session_status(session: &Session) -> String { + let message_count = session.messages.len(); + let mut total_input = 0u32; + let mut total_output = 0u32; + for msg in &session.messages { + if let Some(usage) = msg.usage { + total_input = total_input.saturating_add(usage.input_tokens); + total_output = total_output.saturating_add(usage.output_tokens); + } + } + format!( + "Session {}: {} messages, {} input tokens, {} output tokens", + &session.session_id[0..8], + message_count, + total_input, + total_output + ) +} + +fn render_token_cost(session: &Session) -> String { + let mut total_input = 0u32; + let mut total_output = 0u32; + let mut cache_creation = 0u32; + let mut cache_read = 0u32; + for msg in &session.messages { + if let Some(usage) = msg.usage { + total_input = total_input.saturating_add(usage.input_tokens); + total_output = total_output.saturating_add(usage.output_tokens); + cache_creation = cache_creation.saturating_add(usage.cache_creation_input_tokens); + cache_read = cache_read.saturating_add(usage.cache_read_input_tokens); + } + } + format!( + "Cumulative token usage for this session: + Input tokens: {} + Output tokens: {} + Cache creation tokens: {} + Cache read tokens: {} + Total input: {}", + total_input, + total_output, + cache_creation, + cache_read, + total_input + .saturating_add(cache_creation) + .saturating_add(cache_read) + ) +} + +fn render_version_info() -> String { + let version = env!("CARGO_PKG_VERSION"); + format!("claw-code version {}", version) +} + #[must_use] pub fn handle_slash_command( input: &str, @@ -5334,8 +5388,19 @@ pub fn handle_slash_command( message: render_slash_command_help(), session: session.clone(), }), - SlashCommand::Status - | SlashCommand::Bughunter { .. } + SlashCommand::Status => Some(SlashCommandResult { + message: render_session_status(session), + session: session.clone(), + }), + SlashCommand::Cost => Some(SlashCommandResult { + message: render_token_cost(session), + session: session.clone(), + }), + SlashCommand::Version => Some(SlashCommandResult { + message: render_version_info(), + session: session.clone(), + }), + SlashCommand::Bughunter { .. } | SlashCommand::Commit | SlashCommand::Pr { .. } | SlashCommand::Issue { .. } @@ -5346,7 +5411,6 @@ pub fn handle_slash_command( | SlashCommand::Model { .. } | SlashCommand::Permissions { .. } | SlashCommand::Clear { .. } - | SlashCommand::Cost | SlashCommand::Resume { .. } | SlashCommand::Config { .. } | SlashCommand::Workspace { .. } @@ -5354,7 +5418,6 @@ pub fn handle_slash_command( | SlashCommand::Memory | SlashCommand::Init | SlashCommand::Diff - | SlashCommand::Version | SlashCommand::Export { .. } | SlashCommand::Session { .. } | SlashCommand::Plugins { .. } @@ -6193,7 +6256,6 @@ mod tests { fn ignores_unknown_or_runtime_bound_slash_commands() { let session = Session::new(); assert!(handle_slash_command("/unknown", &session, CompactionConfig::default()).is_none()); - assert!(handle_slash_command("/status", &session, CompactionConfig::default()).is_none()); assert!(handle_slash_command("/sandbox", &session, CompactionConfig::default()).is_none()); assert!( handle_slash_command("/bughunter", &session, CompactionConfig::default()).is_none() @@ -6225,7 +6287,6 @@ mod tests { handle_slash_command("/clear --confirm", &session, CompactionConfig::default()) .is_none() ); - assert!(handle_slash_command("/cost", &session, CompactionConfig::default()).is_none()); assert!(handle_slash_command( "/resume session.json", &session, @@ -6244,7 +6305,6 @@ mod tests { ); assert!(handle_slash_command("/mcp list", &session, CompactionConfig::default()).is_none()); assert!(handle_slash_command("/diff", &session, CompactionConfig::default()).is_none()); - assert!(handle_slash_command("/version", &session, CompactionConfig::default()).is_none()); assert!( handle_slash_command("/export note.txt", &session, CompactionConfig::default()) .is_none() @@ -7206,4 +7266,79 @@ mod tests { let _ = fs::remove_dir_all(config_home); let _ = fs::remove_dir_all(bundled_root); } + + #[test] + fn status_command_shows_session_info() { + use runtime::TokenUsage; + + let mut session = Session::new(); + session.messages.push(ConversationMessage { + role: MessageRole::User, + blocks: vec![ContentBlock::Text { + text: "hello".to_string(), + }], + usage: Some(TokenUsage { + input_tokens: 10, + output_tokens: 20, + cache_creation_input_tokens: 0, + cache_read_input_tokens: 0, + }), + }); + session.messages.push(ConversationMessage { + role: MessageRole::Assistant, + blocks: vec![ContentBlock::Text { + text: "response".to_string(), + }], + usage: Some(TokenUsage { + input_tokens: 5, + output_tokens: 15, + cache_creation_input_tokens: 0, + cache_read_input_tokens: 0, + }), + }); + + let result = handle_slash_command("/status", &session, CompactionConfig::default()) + .expect("/status should be handled"); + assert!(result.message.contains("2 messages")); + assert!(result.message.contains("15 input tokens")); + assert!(result.message.contains("35 output tokens")); + assert_eq!(result.session, session); + } + + #[test] + fn cost_command_shows_token_usage() { + use runtime::TokenUsage; + + let mut session = Session::new(); + session.messages.push(ConversationMessage { + role: MessageRole::User, + blocks: vec![ContentBlock::Text { + text: "hello".to_string(), + }], + usage: Some(TokenUsage { + input_tokens: 100, + output_tokens: 200, + cache_creation_input_tokens: 50, + cache_read_input_tokens: 25, + }), + }); + + let result = handle_slash_command("/cost", &session, CompactionConfig::default()) + .expect("/cost should be handled"); + assert!(result.message.contains("100")); + assert!(result.message.contains("200")); + assert!(result.message.contains("50")); + assert!(result.message.contains("25")); + assert!(result.message.contains("Cumulative token usage")); + assert_eq!(result.session, session); + } + + #[test] + fn version_command_shows_version() { + let session = Session::new(); + let result = handle_slash_command("/version", &session, CompactionConfig::default()) + .expect("/version should be handled"); + assert!(result.message.contains("claw-code version")); + assert_eq!(result.session, session); + } }