From ed7bd05530877f9c812d6146d37e483842268bcf Mon Sep 17 00:00:00 2001 From: nesquena-hermes Date: Tue, 16 Jun 2026 17:21:11 +0000 Subject: [PATCH 1/2] fix(voice): recognize command STT providers in capability probe (#4312) The /api/transcribe/capability probe's resolve_provider() only knew the built-in STT providers, so an explicit stt.provider mapping to a Hermes command-type provider was reported unavailable and the browser fell back unnecessarily. Add command_provider_available(), delegating to the agent's _resolve_command_stt_provider_config(provider, cfg) as the single source of truth, consulted as the final fallthrough after the built-in chain (the agent rejects built-in names internally, so ordering can't shadow a native provider). Added a clarifying comment that command providers are intentionally omitted from the auto-detect tuple to match the agent's _get_provider() precedence. Co-authored-by: LLMinem --- api/upload.py | 15 +++++++++++++ tests/test_voice_transcribe_endpoint.py | 28 +++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/api/upload.py b/api/upload.py index 3125cb7e..c9239518 100644 --- a/api/upload.py +++ b/api/upload.py @@ -457,6 +457,13 @@ def _stt_provider_capability_from_module(stt): # non-WAV input through ffmpeg before invoking the command. return has_local_command() and has_browser_audio_converter() + def command_provider_available(provider): + resolver = getattr(stt, "_resolve_command_stt_provider_config", None) + try: + return callable(resolver) and resolver(provider, cfg_dict) is not None + except Exception: + return False + def resolve_provider(provider): if provider == "local": if bool(getattr(stt, "_HAS_FASTER_WHISPER", False)): @@ -485,6 +492,8 @@ def _stt_provider_capability_from_module(stt): return "none" if provider == "elevenlabs": return "elevenlabs" if bool(env("ELEVENLABS_API_KEY")) else "none" + if command_provider_available(provider): + return provider return "none" explicit = "provider" in cfg_dict @@ -494,6 +503,12 @@ def _stt_provider_capability_from_module(stt): return provider != "none", provider if provider != "none" else configured for candidate in ("local", "local_command", "groq", "openai", "mistral", "xai", "elevenlabs"): + # Command (custom) STT providers are intentionally omitted from this + # auto-detect tuple to mirror the agent's _get_provider() (transcription_tools.py), + # which only auto-selects local > groq > openai and never auto-picks a command + # provider. A command-backed STT activates only via an explicit stt.provider. + # Do NOT add command providers here without matching the agent, or the WebUI + # probe will diverge from what the agent actually resolves. provider = resolve_provider(candidate) if provider != "none": return True, provider diff --git a/tests/test_voice_transcribe_endpoint.py b/tests/test_voice_transcribe_endpoint.py index 1374ebda..0126e94d 100644 --- a/tests/test_voice_transcribe_endpoint.py +++ b/tests/test_voice_transcribe_endpoint.py @@ -156,3 +156,31 @@ def test_handle_transcribe_capability_reports_actual_local_command_fallback(monk assert available is True assert provider == "local_command" + + +def test_handle_transcribe_capability_reports_named_command_provider(monkeypatch): + stt_config = { + "provider": "elevenlabs-gemini", + "providers": { + "elevenlabs-gemini": { + "type": "command", + "command": "/home/user/.hermes/scripts/hermes-elevenlabs-stt.sh {input}", + } + }, + } + fake_mod = types.ModuleType("tools.transcription_tools") + fake_mod.__dict__.update( + { + "_load_stt_config": lambda: stt_config, + "is_stt_enabled": lambda cfg=None: True, + "_HAS_FASTER_WHISPER": False, + "_HAS_OPENAI": False, + "_HAS_MISTRAL": False, + "_resolve_command_stt_provider_config": lambda provider, cfg: cfg["providers"].get(provider), + } + ) + + available, provider = _stt_provider_capability_from_module(fake_mod) + + assert available is True + assert provider == "elevenlabs-gemini" From 33f6bf9754e71b2f6cd8075ea7c711dfabd37eca Mon Sep 17 00:00:00 2001 From: nesquena-hermes Date: Tue, 16 Jun 2026 17:21:29 +0000 Subject: [PATCH 2/2] =?UTF-8?q?docs(changelog):=20v0.51.460=20=E2=80=94=20?= =?UTF-8?q?recognize=20command=20STT=20providers=20in=20voice=20probe=20(#?= =?UTF-8?q?4312)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5b6faa8..0620d25b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ ## [Unreleased] +## [v0.51.460] — 2026-06-16 — Release PU (custom voice providers recognized) + +### Fixed + +- **Command (custom) speech-to-text providers are now recognized by the voice capability probe (#4312).** When `stt.provider` pointed at a Hermes command-type STT provider, the WebUI's `/api/transcribe/capability` probe only knew the built-in providers, so it reported voice input as unavailable and the browser fell back unnecessarily. The probe now delegates command-provider recognition to the agent's own resolver (`_resolve_command_stt_provider_config`) as the single source of truth, consulted after the built-in chain. Thanks @LLMinem. + ## [v0.51.459] — 2026-06-16 — Release PT (your message stays your message) ### Fixed