zeph_commands/handlers/
reasoning_effort.rs1use std::future::Future;
7use std::pin::Pin;
8
9use crate::context::CommandContext;
10use crate::{CommandError, CommandHandler, CommandOutput, SlashCategory};
11
12pub struct ReasoningEffortCommand;
21
22impl CommandHandler<CommandContext<'_>> for ReasoningEffortCommand {
23 fn name(&self) -> &'static str {
24 "/reasoning-effort"
25 }
26
27 fn description(&self) -> &'static str {
28 "Show or set the active provider's runtime reasoning-effort level"
29 }
30
31 fn args_hint(&self) -> &'static str {
32 "[low|medium|high]"
33 }
34
35 fn category(&self) -> SlashCategory {
36 SlashCategory::Configuration
37 }
38
39 fn requires_auth(&self) -> bool {
40 true
41 }
42
43 fn handle<'a>(
44 &'a self,
45 ctx: &'a mut CommandContext<'_>,
46 args: &'a str,
47 ) -> Pin<Box<dyn Future<Output = Result<CommandOutput, CommandError>> + Send + 'a>> {
48 use tracing::Instrument as _;
49 let span = tracing::info_span!("commands.reasoning_effort.handle");
50 Box::pin(
51 async move {
52 let result = ctx.agent.handle_reasoning_effort(args).await;
53 Ok(CommandOutput::message_or_silent(result))
54 }
55 .instrument(span),
56 )
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63 use crate::handlers::test_helpers::{MockDebug, MockMessages, MockSession, make_ctx};
64 use crate::sink::NullSink;
65 use std::assert_matches;
66
67 #[test]
68 fn reasoning_effort_name_and_description() {
69 assert_eq!(ReasoningEffortCommand.name(), "/reasoning-effort");
70 assert!(!ReasoningEffortCommand.description().is_empty());
71 }
72
73 #[tokio::test]
74 async fn reasoning_effort_returns_silent_when_agent_returns_empty() {
75 let mut sink = NullSink;
76 let mut debug = MockDebug;
77 let mut messages = MockMessages;
78 let session = MockSession;
79 let mut agent = crate::NullAgent;
80 let mut ctx = make_ctx(&mut sink, &mut debug, &mut messages, &session, &mut agent);
81 let out = ReasoningEffortCommand.handle(&mut ctx, "").await.unwrap();
82 assert_matches!(out, CommandOutput::Silent);
83 }
84}