Commit graph

9541 commits

Author SHA1 Message Date
canorionen
0fd0161dfd fix(gateway): steer busy voice follow-ups after STT 2026-07-28 11:57:11 -07:00
izumi0uu
aa40f16d3e fix(gateway): transcribe clarify voice replies 2026-07-28 11:57:11 -07:00
Teknium
f76b2b47aa fix(gateway): pass channel_prompt into voice-channel STT events; guard empty transcripts
Two hand-written fixes in the voice input path:

- _handle_voice_channel_input now resolves the bound text channel's
  channel_prompt via the adapter's _resolve_channel_prompt so voice input
  gets the same per-channel context as typed messages (fixes #50149).
- _enrich_message_with_transcription now guards success=True results whose
  transcript is empty/whitespace-only (silence, cut-off, inaudible audio):
  instead of emitting empty quotes the agent gets a clear sentinel note.
  Reimplemented against the current plain-quoted note wording; original
  concept and tests by @deacon-botdoctor in PR #41603 (fixes #41603).
2026-07-28 11:56:37 -07:00
Teknium
a4c9994837 refactor(discord): delegate ffmpeg discovery to shared tools.transcription_tools helper
Keep one owner for PATH/local-prefix ffmpeg discovery: ffmpeg_utils now
delegates to tools.transcription_tools._find_ffmpeg_binary and only adds
the Discord-specific FFMPEG_PATH override and Windows winget fallback on
top (follow-up to PR #60627 by @LauraGPT, fixes #60624).
2026-07-28 11:56:37 -07:00
LauraGPT
9b89da23fb Fix Discord ffmpeg discovery on Windows 2026-07-28 11:56:37 -07:00
Jeffrey Cox
ae8d3e2027 fix(discord): make voice timeouts configurable 2026-07-28 11:56:37 -07:00
Ariel Tov Ben
388f612435 fix(discord): drain pending voice input before disconnect 2026-07-28 11:56:37 -07:00
luyifan
a5b32a721a fix(discord): preserve voice reply threading 2026-07-28 11:56:37 -07:00
PRATHAMESH75
297f5142a6 fix(discord): prepend warm-up silence so TTS first word isn't clipped
Discord's voice socket needs a brief warm-up before receiving clients
actually hear audio; the first ~100-200ms is lost, clipping the first
word/syllable of TTS playback. Prepend a configurable lead of silence to
speech on both playback paths:

- Mixer path: new _lead_silence_bytes() helper prepends PCM silence
  (BYTES_PER_MS constant added to voice_mixer.py) before play_speech, on
  both the reply and the pre-tool ack.
- Legacy FFmpegPCMAudio path: apply -af adelay=<ms>:all=1.

Tunable via discord.voice_fx.lead_silence_ms (default 200, 0 disables).

Fixes #66827
2026-07-28 11:56:37 -07:00
Carlos Diosdado
d22a1ee5be fix(tests): add _FakeAudioSource to discord mock for VoiceMixer inheritance 2026-07-28 11:56:37 -07:00
isheng-eqi
31301c1af7 fix(discord): wire voice input callback at adapter connect time
- Wire adapter._voice_input_callback at connect and reconnect so voice
  transcription is forwarded without requiring /voice join (#60623).
- Add optional text_channel_id and source params to DiscordAdapter
  .join_voice_channel() so automatic/programmatic voice joins can
  establish the text-channel binding needed by _handle_voice_channel_input.
- Add TestVoiceInputCallbackWiring: asserts callback wiring on startup
  and reconnect for Discord adapters with voice attributes.
2026-07-28 11:56:37 -07:00
isheng
79da6adfe9 fix: add _handle_voice_channel_input mock to _make_runner in reconnect tests
PR #61407 accesses self._handle_voice_channel_input in _platform_reconnect_watcher. The test mock runner created via _make_runner() must have this attribute.
2026-07-28 11:56:37 -07:00
Teknium
2008d80a9e test(gateway): regression coverage for platform-aware voice delivery
- tests/gateway/test_base_auto_tts_output_format.py (new): the base
  adapter auto-TTS block passes an explicit .ogg output path on every
  OPUS_VOICE_PLATFORMS member (parametrized from the tts_tool set — the
  single source of truth), keeps .mp3 on non-opus platforms, honors the
  tool's success flag, and stays unique/uuid-based.
- tests/gateway/test_auto_voice_reply_format.py: runner _send_voice_reply
  parametrized across Matrix/Feishu/WhatsApp/Signal (.ogg) alongside the
  existing Telegram/Slack cases; streamed+global-auto-TTS gate regression.
- tests/gateway/test_telegram_voice_caption_markdown.py (new): caption
  MarkdownV2 formatting, entity-rejection plain fallback, overflow skip,
  and no-caption passthrough (#32029).
- test_voice_command.py filename-uuid contract test updated to point at
  build_auto_tts_output_path (construction moved there).
2026-07-28 11:55:48 -07:00
55nx954gn6-debug
28adb86891 fix(gateway): honor global voice.auto_tts in runner voice-reply gate
Salvaged from PR #51196 (@55nx954gn6-debug). _should_send_voice_reply only
consulted the runner's _voice_mode dict (/voice on|voice_only|all), so the
global voice.auto_tts config default — which is synced into each adapter's
_auto_tts_default on gateway connect — was invisible to the runner path.
Net effect: with streaming enabled and only global auto-TTS configured
(no per-chat /voice opt-in), the streamed reply consumed the text, the
base adapter's auto-TTS got text_content=None, and no voice reply was
ever sent (#51867/#23983 remainder).

The runner now also asks the adapter's _should_auto_tts_for_chat(chat_id)
(which encodes per-chat /voice on|off overrides over the global default);
an explicit /voice off chat mode remains a hard override.

Refs #51867 #23983 #51282 #13126
2026-07-28 11:55:48 -07:00
Michel Belleau
33313e88f8 fix(matrix): enforce Ogg/Opus at send_voice boundary, probe metadata off-loop
Salvaged from PR #68063 (@malaiwah). MatrixAdapter.send_voice now transcodes
any non-Ogg audio to Ogg/Opus at the adapter boundary (best-effort — the
original file is sent unchanged when ffmpeg is unavailable), so MSC3245
voice bubbles render even when a caller hands the adapter MP3/WAV audio.
_matrix_voice_metadata_for_file probing now runs via asyncio.to_thread so
ffprobe/ffmpeg subprocess timeouts can't stall the adapter event loop.

The PR's tools/tts_tool.py want_opus hunk was dropped: main's
OPUS_VOICE_PLATFORMS set (PR #73072) already includes matrix; the Matrix
opus-routing test is kept.

Refs #14841
2026-07-28 11:55:48 -07:00
Michel Belleau
34ee3bbf8e fix(matrix): add MSC3245 duration + MSC1767 waveform metadata to voice sends
Salvaged from PR #68063 (base commit, runner-path hunks superseded by the
platform-aware OPUS_VOICE_PLATFORMS fix in this branch). Element and other
Matrix clients render voice bubbles more reliably when m.audio events carry
duration and waveform metadata; probe both best-effort via ffprobe/ffmpeg.
2026-07-28 11:55:48 -07:00
Teknium
4aac89b429 fix(tts): unify TTS text preprocessing behind one shared cleaner
Consolidates all TTS text-preparation paths onto
tools/tts_text_normalize.prepare_spoken_text:

- strip_nonspoken_blocks: removes <think> reasoning blocks (#34213,
  incl. unterminated streaming blocks) and the end-of-turn
  file-mutation verifier footer emitted by run_agent.py (#40772).
- flatten_newlines_for_payload: collapses newlines into sentence
  breaks so newline-sensitive OpenAI-compatible providers (Kokoro)
  speak the whole script instead of truncating at the first newline
  (#9004).
- tools/tts_tool._strip_markdown_for_tts (voice-mode streaming + web
  dashboard path) now delegates to the shared cleaner, with the legacy
  regex pipeline kept as a best-effort fallback.
- hermes_cli/voice.py speak_text and cli.py _voice_speak_response now
  use the shared cleaner instead of their own duplicated regex
  pipelines.
- gateway auto-TTS fallback also strips think blocks.

Tests: tests/tools/test_tts_prepare_spoken.py covers think blocks,
verifier footer, emoji, newline flattening, and the shared-cleaner
wiring on the tool/streaming/gateway paths. Updated the header
expectation in test_voice_cli_integration.py for the heading-fold
behavior of the shared cleaner.

Closes #34213, #9004, #40772
2026-07-28 11:55:01 -07:00
Gabriel Anzziani
ee019d1cc1 test(tts): add lang_code regression tests, document tts.openai.language
Address review feedback on #31693:
- Regression tests assert extra_body == {"lang_code": ...} is forwarded
  when tts.openai.language is configured, and omitted when unset/empty
- Document tts.openai.language as intended for OpenAI-compatible
  endpoints that support lang_code (e.g. Kokoro-FastAPI)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-28 11:55:01 -07:00
Carlos Diosdado
d1e9344716 feat(xai-tts): wire text_normalization parameter
xAI TTS supports a text_normalization boolean that normalizes
written-form text (numbers, abbreviations, symbols) into spoken-form
before synthesis. This parameter was not being sent, leaving users
with literal number/symbol pronunciation.

- Add DEFAULT_XAI_TEXT_NORMALIZATION_DEFAULT = False
- Read tts.xai.text_normalization from config (via _xai_bool_config)
- Attach text_normalization: true to the POST payload when enabled
- Omit the field entirely when unset or explicitly false (API default)
- Add 3 tests: default omission, enabled passthrough, explicit false
2026-07-28 11:55:01 -07:00
Alcibiades Athens
1daa76951b feat(tools): forward OpenAI TTS instructions field through text_to_speech
The `text_to_speech` tool schema accepted only `text` and `output_path`,
so style direction (tone, emotion, pacing, whispering) could never reach
the OpenAI backend — even though `gpt-4o-mini-tts` (Hermes's OpenAI
provider default) treats `instructions` as its primary voice-design
control.

This plumbs an optional `instructions` argument through the tool schema,
the handler lambda, and `text_to_speech_tool()` into
`_generate_openai_tts`, where it is forwarded to
`client.audio.speech.create()` only when truthy. Empty/None values still
omit the key entirely, preserving behavior on `tts-1`/`tts-1-hd` and
strict OpenAI-compatible servers.

The same passthrough unblocks self-hosted OpenAI-compatible voice-design
servers (Qwen3-TTS-VoiceDesign on oMLX, etc.) that are already wired in
via `tts.openai.base_url` — the established convention per #9004 and the
TTS config docs — without inventing a new provider backend.

Tests: `tests/tools/test_tts_instructions.py` covers backend passthrough,
tool-level threading, schema declaration, and the empty-string/absent
omission cases. `tests/tools/test_tts_max_text_length.py` fake_openai
signature widened to accept the new kwarg.

Refs NousResearch/hermes-agent#14196
2026-07-28 11:55:01 -07:00
Hu
8171e8ebb3 feat(tts): expose speed parameter in text_to_speech tool
Add optional 'speed' parameter (0.25-4.0) to the text_to_speech tool
schema and handler. When provided by the model, it overrides the
config-level tts.speed setting, enabling per-request speed control
without config changes.

Use cases:
- Language learning: slow playback (0.5x) for pronunciation practice
- Accessibility: adjustable speed for hearing preferences
- Content review: accelerated playback for long text

The speed value is clamped to [0.25, 4.0] and injected into tts_config
before dispatching to any provider (Edge, OpenAI, MiniMax, etc.), so
all existing provider-level speed handling works transparently.

Includes 3 new tests for tool-level speed injection, clamping, and
config preservation when speed is not specified.
2026-07-28 11:55:01 -07:00
Alexander Russell
ef274a4829 fix(tts): keep Telegram caption on the original reply text
Review follow-up: the spoken script is for synthesis only. Caption
eligibility and payload stay on the original reply, so a long reply
whose normalized script fits the 1024 char limit is still delivered
in full as its own message. Adds the long-original/short-normalized
regression case to the auto-TTS caption tests.
2026-07-28 11:55:01 -07:00
Alexander Russell
a9a9005f31 feat(tts): normalize spoken text (units, symbols, markdown) before synthesis
Auto-TTS previously fed raw chat Markdown and compact symbols straight to the
speech provider, so units were read as stray letters and headings or bullets ran
together. This routes spoken text through a new normalizer,
tools/tts_text_normalize.prepare_spoken_text, that expands units (for example a
temperature written with the degree symbol becomes "degrees Celsius") and
flattens Markdown into a transcript-like script with sentence pauses.

The normalizer is best-effort: if it ever fails the code falls back to the
previous markdown-strip behavior, so auto-TTS keeps working. The Telegram voice
caption uses the same normalized text. Includes a unit test.
2026-07-28 11:55:01 -07:00
Ivan Miao
26dd976fbe fix: align Gemini billing pricing with provider catalog 2026-07-28 11:54:46 -07:00
Ivan Miao
ecebff82d2 fix: support Gemini billing route mapping and pricing update 2026-07-28 11:54:46 -07:00
kosta
e25d516c8c fix(gemini): bump native provider aux default to gemini-3.6-flash
The native Gemini provider profile's default_aux_model and the curated
model picker catalog were still pinned to gemini-3.5-flash, a stale
generation now superseded by gemini-3.6-flash (documented GA). Bump
both so the auxiliary-task default and the picker stay in sync with
the current model.

Contract test asserts the durable lockstep invariant only
(default_aux_model is a member of _PROVIDER_MODELS["gemini"]) rather
than pinning either side to a frozen model-name string, so it doesn't
need updating on the next model-generation bump.
2026-07-28 11:54:46 -07:00
Tikkanaditya Siddartha Jyothi
90b68520ac fix(tts): bound the Piper/KittenTTS model caches with a small LRU
Salvaged from PR #62977 (@Vissirexa) — TTS model-cache half only (the
hindsight turn-buffer half is a different subsystem and was dropped).

_piper_voice_cache and _kittentts_model_cache were keyed by voice/model
with no eviction, and each entry is a whole loaded model (tens of MB).
A surface that sweeps voices pinned one model per voice for the process
lifetime. New _tts_cache_get_or_load() get-or-loads through a small LRU
(_TTS_MODEL_CACHE_MAX=3), refreshing recency on a hit and evicting the
least-recently-used model on a cold miss.
2026-07-28 11:54:26 -07:00
luyifan
60b841bbfb fix(tts): bound upstream response bodies 2026-07-28 11:54:26 -07:00
Teknium
43b8eb84b3 fix(tts): base_url parity audit — Mistral server_url + provider config tests
Class-level sweep following the ElevenLabs salvage (#66311): every cloud
TTS provider section now honors tts.<provider>.base_url. xAI, MiniMax,
Gemini, OpenAI and DeepInfra already did; Mistral (SDK server_url) was
the remaining gap. Adds per-provider config tests locking in the
ElevenLabs environment plumbing and the Mistral server_url passthrough.
2026-07-28 11:54:26 -07:00
Teknium
3356703d1f fix(tts): streaming path also honors tts.openai.api_key from config
Follow-up to salvaged PR #70307 (@aml1973): OpenAIStreamer now checks
tts.openai.api_key (config.yaml) ahead of the env resolver in both
available() and stream(), completing config parity between the sync
and streaming OpenAI TTS paths.
2026-07-28 11:54:26 -07:00
aml1973
861c2fecd2 fix(tts): honor OpenAI config for streaming
Use the shared OpenAI audio key resolver and prefer tts.openai.base_url over the global environment fallback in the Desktop streaming path. Add focused regression coverage for credential and endpoint propagation.
2026-07-28 11:54:26 -07:00
LeonSGP43
efc81a19a6 fix(tts): honor tts.openai.api_key and base_url from config.yaml
Salvaged from PR #26233 (@LeonSGP43), rebased onto the current 3-tuple
_resolve_openai_audio_client_config (is_managed flag, post-#73072 layout).
Same fix independently submitted earlier in PR #26209 (@zccyman) — credit
to both.

Resolution order now mirrors the STT resolver: tts.openai.api_key/base_url
from config.yaml -> VOICE_TOOLS_OPENAI_KEY/OPENAI_API_KEY env (still
honoring config base_url) -> managed gateway. _has_openai_audio_backend
also counts a config api_key as an available backend.

Fixes #26175
2026-07-28 11:54:26 -07:00
dsad
f96eee3a91 fix(xai): pin oauth side-tool base URLs 2026-07-28 11:54:01 -07:00
Fangliquan
0d0ad3f9d9 fix(hermes_cli): lock dashboard xAI active_provider contracts and setup unsuppress
Cover preserve-vs-mark-if-unset for dashboard OAuth, assert TTS setup clears
device_code suppression, and clarify set_active docstring callers.
2026-07-28 11:54:01 -07:00
Fangliquan
f42be94049 fix(hermes_cli): preserve unset-active dashboard xAI OAuth and cover token save modes
Use mark_provider_active_if_unset after dashboard token save, unsuppress
device_code after TTS setup login, and lock default-active plus refresh
active_provider contracts in tests.
2026-07-28 11:54:01 -07:00
Fangliquan
fce06e909d fix(hermes_cli): keep TTS/setup xAI OAuth from switching active chat provider
Save side-tool OAuth tokens without promoting xai-oauth via active_provider
or model.provider so hermes setup tts login no longer hijacks inference routing.
2026-07-28 11:54:01 -07:00
Ben Sheridan-Edwards
bcbae3bf41 fix(stt): cover 401 retry and keep proof image out of the merge diff
- parametrize the OAuth retry regression over HTTP 401 and 403 so both
  documented rejection statuses are exercised
- reword the retry-failure warning: the except block also covers the
  retried request, not just the credential refresh
- drop docs/proof/ from the PR diff; the live-proof screenshot now lives
  on the fork's proof-assets-xai-stt-oauth-retry branch and stays linked
  from the PR description
2026-07-28 11:54:01 -07:00
BenSheridanEdwards
36de3c5c3e fix(stt): retry xAI OAuth after auth rejection 2026-07-28 11:54:01 -07:00
Tobias Safaie geb. Schmidt-Philipp
d889c980f5 fix(stt): prefer explicit xAI API key 2026-07-28 11:54:01 -07:00
Teknium
4eadabb8ea test: add BadRequestError to the fake openai module fixture
_transcribe_openai now imports BadRequestError for the container-retry
path; the managed-gateway fake module needs to provide it.
2026-07-28 11:53:36 -07:00
Teknium
0897e0adb8 test: align dispatch tests with provider-scoped validation and named registration errors
Follow-ups for the salvaged wave: the auto-detect legacy-error test now
stubs the split validators, the unknown-command-provider test expects
the new provider_not_registered error, and _transcribe_local tolerates
a null stt.local config section again.
2026-07-28 11:53:36 -07:00
Teknium
9467dc135f fix(stt): lock local model load; allow keyless local OpenAI-compatible STT
Two small fresh fixes on top of the salvage wave:

- Wrap the check-then-load of the module-global faster-whisper model in a
  double-checked threading.Lock so concurrent voice messages can't both
  download/load the model (#24767).
- Treat an empty stt.openai.api_key as no-auth when stt.openai.base_url
  points at a loopback/RFC-1918/.local host, so local OpenAI-compatible
  STT servers (faster-whisper-server, speaches, vLLM whisper) work
  without a sham api_key value. Reimplements the idea from PR #25193 —
  credit @nnnet.

Co-authored-by: nnnet <nnnet@users.noreply.github.com>
2026-07-28 11:53:36 -07:00
Dennis
f50a7c307a fix(stt): preprocess .silk voice notes before transcription
Decode WeChat/QQ SILK v3 voice notes to WAV inside transcribe_audio so
any platform that caches a .silk file gets STT for free (same central-
normalization philosophy as the outbound container repair). pilk is
lazy-installed on first use (stt.silk in tools/lazy_deps.py) instead of
being added to the voice extra.

Fixes the inbound half of #32196.

(cherry picked from commit e5db79369d; reworked to compose with the
provider-scoped upload size cap and to lazy-dep pilk)
2026-07-28 11:53:36 -07:00
Zehua Wang
7a56ab2aa2 fix(stt): validate selected voice provider availability 2026-07-28 11:53:36 -07:00
Zehua Wang
eaa2dd6d09 fix(stt): check selected provider (not any) + plugin support
PR review feedback:
- Replace _has_any_command_stt_provider() with selected-provider
  check via _resolve_command_stt_provider_config()
- Add _check_plugin_stt_provider() for plugin-registered backends
- Add tests: selected command, unrelated command (should NOT pass),
  and plugin provider path
2026-07-28 11:53:36 -07:00
LauraGPT
7d2b8a3cad fix(stt): anchor qwen asr envelope stripping 2026-07-28 11:53:36 -07:00
LauraGPT
d219392e5b fix(stt): anchor Qwen3-ASR envelope stripping 2026-07-28 11:53:36 -07:00
LauraGPT
517b8debbd fix(stt): strip Qwen3-ASR response prefix
Normalize the structured <asr_text> marker after extracting text from string, SDK object, and dictionary transcription responses. Preserve the current provider-aware STT configuration architecture.

Refreshes #8773 on current main.

Co-authored-by: angelos <angelos@oikos.lan.home.malaiwah.com>

Assisted-by: Codex:gpt-5.6
2026-07-28 11:53:36 -07:00
luyifan
a5074c0ca8 fix(stt): report unregistered configured providers 2026-07-28 11:53:36 -07:00
Richard Jang
3290c18247 fix: handle missing transcription module gracefully 2026-07-28 11:53:36 -07:00