hermes-agent/tests/test_cli_reasoning_command.py
Aum08Desai e6b325cc24 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>
2026-03-11 06:11:53 -07:00

137 lines
4.1 KiB
Python

"""Tests for /reasoning slash command in HermesCLI."""
from unittest.mock import patch
import pytest
def _make_cli(**kwargs):
"""Create a HermesCLI instance with minimal mocking."""
import cli as _cli_mod
from cli import HermesCLI
clean_config = {
"model": {
"default": "anthropic/claude-opus-4.6",
"base_url": "https://openrouter.ai/api/v1",
"provider": "auto",
},
"display": {"compact": False, "tool_progress": "all"},
"agent": {"reasoning_effort": "medium"},
"terminal": {"env_type": "local"},
}
clean_env = {"LLM_MODEL": "", "HERMES_MAX_ITERATIONS": ""}
with (
patch("cli.get_tool_definitions", return_value=[]),
patch.dict("os.environ", clean_env, clear=False),
patch.dict(_cli_mod.__dict__, {"CLI_CONFIG": clean_config}),
):
return HermesCLI(**kwargs)
# -- setting valid effort levels -------------------------------------------
@pytest.mark.parametrize("level", ["low", "medium", "high", "xhigh"])
def test_reasoning_command_sets_effort_and_persists(level):
cli_obj = _make_cli()
cli_obj.agent = object() # ensure command forces re-init
with patch("cli.save_config_value", return_value=True) as mock_save:
keep_running = cli_obj.process_command(f"/reasoning {level}")
assert keep_running is True
assert cli_obj.reasoning_config == {"enabled": True, "effort": level}
assert cli_obj.agent is None
mock_save.assert_called_once_with("agent.reasoning_effort", level)
def test_reasoning_command_sets_none_disables_reasoning():
cli_obj = _make_cli()
cli_obj.agent = object()
with patch("cli.save_config_value", return_value=True) as mock_save:
keep_running = cli_obj.process_command("/reasoning none")
assert keep_running is True
assert cli_obj.reasoning_config == {"enabled": False}
assert cli_obj.agent is None
mock_save.assert_called_once_with("agent.reasoning_effort", "none")
# -- rejecting invalid levels ---------------------------------------------
@pytest.mark.parametrize("bad_level", ["ultra", "minimal", "max", "off", "0", "150"])
def test_reasoning_command_rejects_invalid_level(capsys, bad_level):
cli_obj = _make_cli()
before = cli_obj.reasoning_config
with patch("cli.save_config_value", return_value=True) as mock_save:
keep_running = cli_obj.process_command(f"/reasoning {bad_level}")
out = capsys.readouterr().out
assert keep_running is True
assert "Invalid reasoning level" in out
assert cli_obj.reasoning_config == before
mock_save.assert_not_called()
# -- display current level -------------------------------------------------
def test_reasoning_shows_current_effort(capsys):
cli_obj = _make_cli()
cli_obj.reasoning_config = {"enabled": True, "effort": "high"}
cli_obj.process_command("/reasoning")
out = capsys.readouterr().out
assert "Reasoning effort: high" in out
def test_reasoning_shows_default_when_none_set(capsys):
cli_obj = _make_cli()
cli_obj.reasoning_config = None
cli_obj.process_command("/reasoning")
out = capsys.readouterr().out
assert "medium (default)" in out
def test_reasoning_shows_disabled_when_none_effort(capsys):
cli_obj = _make_cli()
cli_obj.reasoning_config = {"enabled": False}
cli_obj.process_command("/reasoning")
out = capsys.readouterr().out
assert "none (reasoning disabled)" in out
# -- case insensitivity ----------------------------------------------------
def test_reasoning_command_is_case_insensitive(capsys):
cli_obj = _make_cli()
with patch("cli.save_config_value", return_value=True):
cli_obj.process_command("/reasoning HIGH")
assert cli_obj.reasoning_config == {"enabled": True, "effort": "high"}
# -- config save failure ---------------------------------------------------
def test_reasoning_shows_session_only_on_save_failure(capsys):
cli_obj = _make_cli()
with patch("cli.save_config_value", return_value=False):
cli_obj.process_command("/reasoning high")
out = capsys.readouterr().out
assert "session only" in out