feat(desktop): list config-defined command TTS/STT providers in settings - #62459
feat(desktop): list config-defined command TTS/STT providers in settings#62459laurinaitis wants to merge 1 commit into
Conversation
teknium1
left a comment
There was a problem hiding this comment.
Thanks for addressing a real desktop settings gap. Current main still limits enumOptionsFor to built-ins plus the selected value (apps/desktop/src/app/settings/helpers.ts:141-149).
Problems
apps/desktop/src/app/settings/helpers.ts:143scans immediatetts/sttchildren. Current dispatch resolves custom providers fromtts.providers.<name>(tools/tts_tool.py:422-429) andstt.providers.<name>(tools/transcription_tools.py:288-301), so canonical command providers are not returned by this implementation.- The new fixtures use direct
tts.<name>/stt.<name>blocks (helpers.test.ts:180-185,:199), so they do not cover the documented and primary runtime layout. - The change also replaces the canonical
@/lib/textre-export with duplicate helpers (helpers.ts:5-9), reversing the consolidation in7e6d60aadccc4d513aaf6e1869952c68b5135d71.
Suggested changes
- Enumerate
section.providers, test canonical TTS and STT fixtures, and retain the existing text-helper re-export.
Automated hermes-sweeper review.
| // currently active (otherwise, once you switch away from a custom provider it | ||
| // drops off the list and can only be reselected by hand-editing config). | ||
| function commandProviderNames(config: HermesConfigRecord, section: 'tts' | 'stt'): string[] { | ||
| const block = getNested(config, section) |
There was a problem hiding this comment.
Command providers are canonically nested under tts.providers.<name> / stt.providers.<name> (tools/tts_tool.py:422-429, tools/transcription_tools.py:288-301). Scanning the section itself sees providers as one object with no type, so documented providers are never added. Please enumerate the nested providers record and update the tests to use that shape.
| // Canonical implementations live in @/lib/text; re-exported here so the many | ||
| // settings/capabilities call sites keep their import path. | ||
| export { asText, includesQuery, prettyName } from '@/lib/text' | ||
| export const asText = (v: unknown): string => (typeof v === 'string' ? v : v == null ? '' : String(v)) |
There was a problem hiding this comment.
Please retain the existing @/lib/text import and re-export. Current main deliberately centralizes these helpers there (7e6d60aadccc4d513aaf6e1869952c68b5135d71); this duplicate definition is unrelated to the provider-picker change.
1863ddb to
701b4c5
Compare
|
Thanks for the review — both points are addressed in the latest push. 1. Canonical The command-provider predicate now also matches 2. All 21 tests in |
The Settings > Voice provider dropdowns (tts.provider / stt.provider) only offer the built-in providers plus whatever value is currently set. Custom `type: command` providers declared in config.yaml aren't selectable — and once you switch away from one it drops off the list, so you can only return to it by hand-editing config. enumOptionsFor now merges in the names of any `type: command` entries under the tts/stt config sections, so local command-backed engines appear alongside the built-ins and can be switched freely from the UI. Enumeration mirrors the runtime's own resolution so the dropdown can only offer a name the runtime would actually honour: the canonical `<section>.providers.<name>` location plus the back-compat top-level `<section>.<name>` block, the optional `type:` discriminator, and the built-in-name guard. The guard compares against the runtime's built-in sets rather than the ENUM_OPTIONS display list, which is not a substitute — it already omits `deepinfra` (TTS) and `deepinfra`/`local_command` (STT), so a `providers.deepinfra` command block would otherwise be offered as selectable while the runtime dispatches to the native backend instead. - helpers.ts: add commandProviderNames() + the built-in guard; merge for tts.provider + stt.provider - helpers.test.ts: cover both sections, incl. that non-command config blocks aren't offered and that built-ins absent from the display list are never offered as command providers
701b4c5 to
9f8fe84
Compare
|
Prior art I should have cited when I filed this: #41232 and #40338. Both touch these dropdowns. #41232 (merged 2026-06-07) added the #40338 (@lost9999, opened 2026-06-06) targets the same gap and predates this PR by five weeks. It changes the schema in The sweeper review on #40338 (2026-07-14) reached the same conclusion and asked for the merge to be done in the desktop resolver, covering configured-but-inactive command providers. That's what this PR does. I filed it on 07-11, before that review existed, so the timing is coincidence rather than a response to it. The overlap is in intent and the surfaces are disjoint: #40338 fixes the server schema and plugin names, this fixes the desktop resolver. Both can land. @lost9999 raised it first - if you'd rather carry one PR, I'm happy to fold this diff into #40338 or close this in its favour. Your call on the vehicle. Scope here is providers declared in Also pushed since the last review. Built-in names are now excluded using the runtime's own sets instead of the display list. Rebased onto main - the branch was 809 commits behind, which left the desktop settings suite red for unrelated reasons. Settings suite is 70 passing, eslint clean on the changed lines. Separately: the |
|
Merged via PR #67209 — your commit was cherry-picked onto current main with your authorship preserved in git log. We verified the guard sets against the current runtime (BUILTIN_TTS_PROVIDERS / BUILTIN_STT_PROVIDERS) and everything matched. Thanks for the contribution! |
Problem
Settings → Voice → Text-To-Speech Provider (and the STT equivalent) list the built-in providers plus whatever value is currently set. Custom
type: commandproviders defined inconfig.yamlaren't offered, so:config.yaml.This bites anyone running a local command-backed TTS/STT engine, e.g. a self-hosted OpenAI-compatible voice server wired up as a
commandprovider.Fix
enumOptionsFor(apps/desktop/src/app/settings/helpers.ts) merges in the names of user-defined command providers, so local command-backed engines appear alongside the built-ins and can be switched freely.Enumeration mirrors the runtime's own resolution, so the dropdown can only offer a name the runtime would honour:
commandProviderNamesreads the canonicaltts.providers.<name>/stt.providers.<name>, plus the back-compat top-leveltts.<name>/stt.<name>block, deduped - matching_get_named_provider_config(tools/tts_tool.py) and_get_named_stt_provider_config(tools/transcription_tools.py)._is_command_provider_config:typeis optional and case/space-insensitive (absent or normalising tocommand), with a required non-emptycommand. Non-command blocks (a built-in's own settings sub-object) and theproviderscontainer are not offered.BUILTIN_TTS_PROVIDERS/BUILTIN_STT_PROVIDERS, matching theprovider.lower().strip()guard in_resolve_command_provider_config(tools/tts_tool.py:472).ENUM_OPTIONSis a display list and isn't a substitute here: it omitsdeepinfra(TTS) anddeepinfra/local_command(STT), so filtering on it would offer aproviders.deepinfracommand block as selectable while the runtime dispatches to the native backend.Existing behaviour is unchanged: built-ins, then the current value appended if unknown. Nothing about provider dispatch changes.
Scope is providers declared in
config.yaml. Plugin-registered provider names live in the runtime registry (hermes_cli/plugins.py) and would need a backend API to surface, so they're out of this diff.Tests
helpers.test.tscovers, for both TTS and STT: custom command providers surfacing from the canonicalprovidersnesting and the legacy top-level shape; a bare{ command: … }block with notype:; a misconfigured{ type: command }with nocommandexcluded; a non-command block coinciding with a built-in name not duplicated; theproviderscontainer not offered; and built-in names never offered as command providers, including the ones the display list omits. The last two cases fail on the previous filter.Desktop settings suite: 70 passing.
eslintclean on the changed lines. Rebased onto main;tsc --noEmitshows four pre-existing errors inassistant-uifiles, unchanged from a stock main checkout and untouched here.Related
#50081 and #50612 added command-type providers; this makes them selectable from the settings UI. Adjacent settings/config UX: #50698.
Prior art on these dropdowns, which I should have cited when filing:
ENUM_OPTIONS. Static list, so it can't carry a config-defined name; on main such a provider is visible only while it's the active value.hermes_cli/web_server.py. That schema doesn't reach the desktop picker -ConfigFieldresolvesenumOptions ?? schema.options(config-settings.tsx:119), soENUM_OPTIONSwins for these keys. The sweeper review there (2026-07-14) asked for the merge to be implemented in the desktop resolver, which is what this does. Happy to fold this into feat: surface custom and plugin voice providers in settings #40338 if maintainers prefer one vehicle.