Skip to main content

zeph_commands/handlers/
reasoning_effort.rs

1// SPDX-FileCopyrightText: 2026 Andrei G <bug-ops>
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! `/reasoning-effort` command handler — runtime reasoning-effort level control.
5
6use std::future::Future;
7use std::pin::Pin;
8
9use crate::context::CommandContext;
10use crate::{CommandError, CommandHandler, CommandOutput, SlashCategory};
11
12/// Show or set the active provider's runtime reasoning-effort level.
13///
14/// - `/reasoning-effort` — display the current level.
15/// - `/reasoning-effort low|medium|high` — set the level.
16///
17/// Session-only: never persisted across restarts or `/provider` switches. Supported by
18/// Claude (adaptive thinking), OpenAI/Compatible (`reasoning_effort`), and Gemini (thinking
19/// level); other providers return an explicit "not supported" message.
20pub 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}