From 165bcb705d9d0c53cfc0b7988989de4bd270927d Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Wed, 2 Sep 2026 21:13:10 -0700 Subject: [PATCH] refactor(hermes_cli): collapse announce-gated wake prints, provider-family normalize helper, stream boundary predicate --- hermes_cli/cli_model_switch_mixin.py | 53 ++++++++++++++-------------- hermes_cli/cli_stream_mixin.py | 15 +++----- hermes_cli/cli_voice_mixin.py | 32 +++++++---------- 3 files changed, 43 insertions(+), 57 deletions(-) diff --git a/hermes_cli/cli_model_switch_mixin.py b/hermes_cli/cli_model_switch_mixin.py index a62b481cf2..2300415dce 100644 --- a/hermes_cli/cli_model_switch_mixin.py +++ b/hermes_cli/cli_model_switch_mixin.py @@ -244,11 +244,18 @@ class CLIModelSwitchMixin: current_model = canonical changed = True - def _set_mode(resolved_mode) -> None: + def _adopt_with_mode(normalize, api_mode_of, notice) -> bool: + """Provider families that also own the wire protocol: adopt id, then sync api_mode.""" nonlocal changed - if resolved_mode != self.api_mode: - self.api_mode = resolved_mode - changed = True + try: + _adopt(normalize(current_model), notice) + resolved_mode = api_mode_of(current_model) + if resolved_mode != self.api_mode: + self.api_mode = resolved_mode + changed = True + except Exception: + pass + return changed try: from hermes_cli.model_normalize import ( @@ -265,32 +272,24 @@ class CLIModelSwitchMixin: pass if resolved_provider == "copilot": - try: - from hermes_cli.models import copilot_model_api_mode, normalize_copilot_model_id - _adopt( - normalize_copilot_model_id(current_model, api_key=self.api_key), - lambda new: f"Normalized Copilot model '{current_model}' to '{new}'.", - ) - _set_mode(copilot_model_api_mode(current_model, api_key=self.api_key)) - except Exception: - pass - return changed + from hermes_cli.models import copilot_model_api_mode, normalize_copilot_model_id + return _adopt_with_mode( + lambda m: normalize_copilot_model_id(m, api_key=self.api_key), + lambda m: copilot_model_api_mode(m, api_key=self.api_key), + lambda new: f"Normalized Copilot model '{current_model}' to '{new}'.", + ) from hermes_cli.models import opencode_provider_family if opencode_provider_family(resolved_provider) is not None: - try: - from hermes_cli.models import normalize_opencode_model_id, opencode_model_api_mode - _adopt( - normalize_opencode_model_id(resolved_provider, current_model), - lambda new: ( - f"Stripped provider prefix from '{current_model}'; " - f"using '{new}' for {resolved_provider}." - ), - ) - _set_mode(opencode_model_api_mode(resolved_provider, current_model)) - except Exception: - pass - return changed + from hermes_cli.models import normalize_opencode_model_id, opencode_model_api_mode + return _adopt_with_mode( + lambda m: normalize_opencode_model_id(resolved_provider, m), + lambda m: opencode_model_api_mode(resolved_provider, m), + lambda new: ( + f"Stripped provider prefix from '{current_model}'; " + f"using '{new}' for {resolved_provider}." + ), + ) if resolved_provider != "openai-codex": return changed diff --git a/hermes_cli/cli_stream_mixin.py b/hermes_cli/cli_stream_mixin.py index 7d526eccc2..b881020a2b 100644 --- a/hermes_cli/cli_stream_mixin.py +++ b/hermes_cli/cli_stream_mixin.py @@ -305,16 +305,11 @@ class CLIStreamMixin: if idx == -1: break preceding = self._stream_prefilt[:idx] - last_nl = preceding.rfind("\n") - if last_nl == -1: - # No newline in buffer — boundary only if the last emit was a - # newline AND only whitespace precedes the tag (idx == 0 included). - is_block_boundary = ( - getattr(self, "_stream_last_was_newline", True) - and preceding.strip() == "" - ) - else: - is_block_boundary = preceding[last_nl + 1:].strip() == "" + # Boundary: only whitespace since the last newline — or, with no newline + # buffered yet, since the last emit (which must have ended a line). + is_block_boundary = preceding[preceding.rfind("\n") + 1:].strip() == "" and ( + "\n" in preceding or getattr(self, "_stream_last_was_newline", True) + ) if is_block_boundary: if preceding: self._emit_stream_text(preceding) diff --git a/hermes_cli/cli_voice_mixin.py b/hermes_cli/cli_voice_mixin.py index 0fa69ac13a..c80ce39e11 100644 --- a/hermes_cli/cli_voice_mixin.py +++ b/hermes_cli/cli_voice_mixin.py @@ -652,41 +652,37 @@ class CLIVoiceMixin: def _start_wake_word_listener(self, announce: bool = False) -> bool: """Build + start the hotword detector. Returns True on success.""" from cli import _ACCENT, _DIM, _RST, _cprint + say = _cprint if announce else (lambda *_a: None) try: from tools.wake_word import ( check_wake_word_requirements, load_wake_word_config, owns_listener, start_listening ) except Exception as e: - if announce: - _cprint(f"{_DIM}Wake word unavailable: {e}{_RST}") + say(f"{_DIM}Wake word unavailable: {e}{_RST}") return False if getattr(self, "_wake_word_active", False) and owns_listener(self): - if announce: - _cprint(f"{_DIM}Wake word is already listening.{_RST}") + say(f"{_DIM}Wake word is already listening.{_RST}") return True self._wake_word_active = False cfg = load_wake_word_config() reqs = check_wake_word_requirements(cfg) if not reqs["available"]: - if announce: - _cprint(f"\n{_ACCENT}Wake word requirements not met:{_RST}") - if reqs.get("hint"): - _cprint(f" {_DIM}{reqs['hint']}{_RST}") + say(f"\n{_ACCENT}Wake word requirements not met:{_RST}") + if reqs.get("hint"): + say(f" {_DIM}{reqs['hint']}{_RST}") return False - - if announce and not reqs.get("deps_available", True): + if not reqs.get("deps_available", True): # Fresh install: the engine constructor lazy-installs its deps (onnxruntime is # a large wheel) — tell the user why this is slow. - _cprint(f"{_DIM}Installing wake word engine (first use — this may take a minute)...{_RST}") + say(f"{_DIM}Installing wake word engine (first use — this may take a minute)...{_RST}") self._wake_start_new_session = bool(cfg.get("start_new_session", True)) try: start_listening(self._on_wake_word, owner=self, config=cfg) except Exception as e: - if announce: - _cprint(f"\n{_DIM}Failed to start wake word: {e}{_RST}") + say(f"\n{_DIM}Failed to start wake word: {e}{_RST}") return False self._wake_word_active = True @@ -694,9 +690,8 @@ class CLIVoiceMixin: import cli as _cli _cli._cli_wake_owner = self self._start_wake_watchdog() - if announce: - _cprint(f"\n{_ACCENT}Wake word listening{_RST} " - f"{_DIM}(say \"{reqs['phrase']}\" — /wake off to stop){_RST}") + say(f"\n{_ACCENT}Wake word listening{_RST} " + f"{_DIM}(say \"{reqs['phrase']}\" — /wake off to stop){_RST}") return True def _stop_wake_word_listener(self, announce: bool = False): @@ -714,10 +709,7 @@ class CLIVoiceMixin: if _cli._cli_wake_owner is self: _cli._cli_wake_owner = None if announce: - if was_active: - _cprint(f"{_DIM}Wake word stopped.{_RST}") - else: - _cprint(f"{_DIM}Wake word is not running.{_RST}") + _cprint(f"{_DIM}Wake word {'stopped' if was_active else 'is not running'}.{_RST}") def _on_wake_word(self): """Fired after the detector hears the wake phrase."""