feat(cli): add /reasoning slash command to manage reasoning effort

Cherry-picked from PR #789 by Aum08Desai, rebased onto current main
with conflict resolution and improvements:

- Added /reasoning command: view current level or set to none|low|medium|high|xhigh
- Persists to config via save_config_value, forces agent re-init
- Resolved conflict with COMMANDS_BY_CATEGORY refactor (added to Configuration category)
- Restricted valid levels to none, low, medium, high, xhigh (removed 'minimal')
- Updated _parse_reasoning_config in cli.py and _load_reasoning_config in gateway/run.py
- Improved display messages (show all valid options, clearer defaults/disabled state)
- Added EXPECTED_COMMANDS entry for regression guard
- Expanded test suite: 16 tests covering all levels, rejection, display, case insensitivity,
  config save failure

Co-authored-by: Aum08Desai <145567217+Aum08Desai@users.noreply.github.com>
This commit is contained in:
Aum08Desai 2026-03-11 06:11:53 -07:00 committed by teknium1
parent c837ef949d
commit e6b325cc24
5 changed files with 176 additions and 5 deletions

37
cli.py
View file

@ -115,7 +115,7 @@ def _load_prefill_messages(file_path: str) -> List[Dict[str, Any]]:
def _parse_reasoning_config(effort: str) -> dict | None:
"""Parse a reasoning effort level into an OpenRouter reasoning config dict.
Valid levels: "xhigh", "high", "medium", "low", "minimal", "none".
Valid levels: "none", "low", "medium", "high", "xhigh".
Returns None to use the default (medium), or a config dict to override.
"""
if not effort or not effort.strip():
@ -123,7 +123,7 @@ def _parse_reasoning_config(effort: str) -> dict | None:
effort = effort.strip().lower()
if effort == "none":
return {"enabled": False}
valid = ("xhigh", "high", "medium", "low", "minimal")
valid = ("low", "medium", "high", "xhigh")
if effort in valid:
return {"enabled": True, "effort": effort}
logger.warning("Unknown reasoning_effort '%s', using default (medium)", effort)
@ -2366,6 +2366,37 @@ class HermesCLI:
print(" Usage: /personality <name>")
print()
def _handle_reasoning_command(self, cmd: str):
"""Handle the /reasoning command to view or set reasoning effort."""
parts = cmd.split(maxsplit=1)
valid_efforts = ("none", "low", "medium", "high", "xhigh")
if len(parts) == 1:
if self.reasoning_config is None:
current = "medium (default)"
elif self.reasoning_config.get("enabled") is False:
current = "none (reasoning disabled)"
else:
current = self.reasoning_config.get("effort", "medium")
print(f" Reasoning effort: {current}")
print(" Usage: /reasoning <none|low|medium|high|xhigh>")
return
effort = parts[1].strip().lower()
if effort not in valid_efforts:
print(f" (._.) Invalid reasoning level: '{effort}'")
print(" Valid levels: none, low, medium, high, xhigh")
return
self.reasoning_config = _parse_reasoning_config(effort)
self.agent = None # Force re-init with new reasoning config
if save_config_value("agent.reasoning_effort", effort):
print(f" (^_^)b Reasoning effort set to: {effort} (saved to config)")
else:
print(f" (^_^) Reasoning effort set to: {effort} (session only)")
def _handle_cron_command(self, cmd: str):
"""Handle the /cron command to manage scheduled tasks."""
parts = cmd.split(maxsplit=2)
@ -2830,6 +2861,8 @@ class HermesCLI:
elif cmd_lower.startswith("/personality"):
# Use original case (handler lowercases the personality name itself)
self._handle_personality_command(cmd_original)
elif cmd_lower.startswith("/reasoning"):
self._handle_reasoning_command(cmd_original)
elif cmd_lower == "/retry":
retry_msg = self.retry_last()
if retry_msg and hasattr(self, '_pending_input'):