Commit Graph

127 Commits

Author SHA1 Message Date
Teknium d7cdfbc36b refactor(stt): openai-availability reason helper, one-line section banners, docstring compaction 2026-09-02 16:12:29 -07:00
Teknium 60aa491f3d refactor(stt): local backend detection helper, CPU-eviction helper, module docstrings and import packing 2026-09-02 16:10:20 -07:00
Teknium 063517e745 refactor(stt): shared lazy-install helper, compact trim/validation/local-kwargs bodies, repack re-export block 2026-09-02 16:04:57 -07:00
Teknium 5844839639 refactor(stt): fold no-provider error tail, drop _normalize_local_command_model alias, compact dispatcher call sites 2026-09-02 15:59:38 -07:00
Teknium bf427a20a1 refactor(stt): collapse duplicate validators/process-error detail/CAF converter, managed-gateway helper, read-block guard 2026-09-02 15:52:30 -07:00
Teknium 925f8061ff refactor(stt): provider spec table + builtin model table, shared OpenAI-SDK/REST failure helpers, trim dead re-exports 2026-09-02 15:46:46 -07:00
Teknium 282e63f81c refactor(stt): split transcription_tools into common/audio/local/cloud/command sibling modules (re-imported into origin) 2026-09-02 15:36:59 -07:00
Teknium 2ef1e8e4e0 refactor(tools/voice): extract tts delivery + wake_word engines; dedupe transcription/voice_mode helpers; compact tts providers 2026-09-02 14:45:15 -07:00
Teknium d4cec15b47 refactor(tools): first-wave simplification of tools/ (file ops split, lazy_deps, code_exec, approval, browser, delegate, mcp, skills, terminal, voice, media)
Behavior-neutral structural pass over tools/*: god-file extractions into
sibling modules (file_operations_common/lint/search, file_tools_paths/
read_tracking/write, code_execution_env/rpc, tool_search_catalog/names/
validation, tts_command_provider, ...), duplicate helper unification,
if/elif -> dispatch tables, dead-code removal, docstring compaction.
Tool schemas (get_tool_definitions) verified byte-identical to base.
2026-09-02 14:43:45 -07:00
liuhao1024 5d8b031514 fix(stt): surface the selection-specific error for explicit openai STT
When the managed openai-audio gateway is unavailable,
_resolve_openai_audio_client_config() raises a ValueError that names the
blocker (and, for managed-Nous users, the `hermes tools` remediation).
The boolean probe in _get_provider's explicit-openai branch flattened
that into False, so the log claimed "no API key available" and the
transcription result returned the all-provider install hint -- pointing
operators at unrelated setup instead of their managed route (#93045).

Resolve the config directly in the branch so the warning names the real
blocker, and let the dispatch's "none" fallback surface the
selection-specific error for an explicit openai choice. No fallback is
added: an unavailable selection still resolves to "none", it just
reports why.
2026-08-23 18:25:35 -07:00
Teknium aa3c5e59d3 fix(tools): honor raw stt.provider: local; finish _reconfigure_provider provider-string migration
Two real gaps the CI-red sibling tests exposed:

- read_selection() treated EVERY raw stt.provider: local as the legacy
  DEFAULT_CONFIG seed and reported no-selection — but the seed never
  reached config.yaml (save_config strips schema defaults), so a
  picker- or hand-written local pick was silently discarded and the
  autodetect ladder could route an explicit local user to cloud STT.
  A raw 'local' is now a genuine selection; the merged-view ambiguity
  note replaces the over-broad shim (mirror comment updated in
  nous_subscription._selected_provider and _get_provider).

- _reconfigure_provider was half-migrated: the tts/stt/browser/web
  branches and the managed-category fallthrough still wrote
  use_gateway flags and vendor names for managed rows. They now write
  the single provider string ('nous' for managed rows) and pop the
  legacy key, matching _write_provider_config.
2026-08-19 16:10:01 -07:00
Teknium 099258ef48 fix(voice): route TTS/STT OpenAI audio on the stored selection, not credentials
Both _resolve_openai_audio_client_config resolvers now switch on the
stored provider string: 'nous' (or legacy use_gateway: true) => managed
openai-audio gateway only, erroring by selection name when unentitled —
the STT twin previously never read the stored gateway intent at all, so
a direct OPENAI_API_KEY silently overrode the Nous Subscription pick;
stored vendor => direct credentials only with a selection-naming error
on missing keys (no silent managed fallback); never-configured keeps the
legacy ladder. DEFAULT_CONFIG stops seeding stt.provider: local, and the
seeded value on existing configs is treated as no-selection so autodetect
keeps working for that installed base.
2026-08-19 16:10:01 -07:00
Hans 52eb8eb533 feat(plugins): add pre_transcription hook and STT prompt threading
Adds a pre_transcription transform hook (prompt/language/model mutable,
file_path read-only, last-writer-wins per the transform_* convention)
fired before any STT backend, threads prompt to faster-whisper
(initial_prompt) and OpenAI/Groq/Mistral/DeepInfra (prompt), adds an
optional stt.prompt config key on the same plumbing, and keeps the
no-hook dispatch path byte-identical. Fixes #64168.

Documents the new surface for users: a "Transcription prompt
(vocabulary hints)" subsection in the configuration guide (composition
order, per-provider support matrix, length contract, privacy warning),
a pre_transcription entry in the hooks reference, and the mirrored row
in the plugins hook table.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AG6LyYMvHC2o6HbVUozmVR
2026-08-12 19:01:30 -07:00
kshitij 72c63aa586 fix(stt): close idle-unload races — strong model ref, single long-lived watcher
Review pass on the idle-unload feature found two material concurrency
bugs; both fixed here with a regression guard:

1. Unload-vs-use null deref (HIGH): _transcribe_local re-read the
   module global _local_model at the transcribe call site. An idle
   unload firing between the model load and transcribe() evaluated
   None.transcribe → AttributeError → user-visible 'Local
   transcription failed'. The window was real: the idle timer was only
   touched AFTER a successful transcription, so a voice note arriving
   exactly as the timeout expired raced the watcher directly.
   Fix: bind a strong local reference under the model lock and use it
   for the whole transcription (the watcher can null the global at any
   time; this in-flight call keeps its instance — the generator holds
   self, so no use-after-free). Also touch the idle timer at the START
   of transcription so a long in-flight transcribe can't be counted as
   idle time. The CUDA-fallback retry path gets the same treatment
   (locked global write, local ref use).

2. Watcher replacement race + response-path join (MEDIUM/HIGH): the
   old design stopped and re-started the watcher after EVERY
   transcription with an unlocked set/join(5)/clear/start sequence on
   shared globals. Two concurrent voice messages could interleave to
   leave TWO live watchers (one with a stale, shorter timeout — a
   raised unload_after_idle_seconds could still unload on the old
   value), and the join(timeout=5) sat on the user-visible response
   path (a watcher blocked on _local_model_lock during a concurrent
   multi-second model load stalls the reply up to 5s).
   Fix: single long-lived watcher under a management lock — started
   only when none is alive (per-transcription cost: one lock + one
   is_alive check), re-reads the configured timeout from config every
   cycle (config edits now apply within one 30s interval, without
   waiting for the next voice message — previously undocumented), and
   stands down without unloading when the timeout is set to 0
   mid-idle.

Tests: 17 now — idempotent start (same thread, no churn), config
re-read + stand-down-when-disabled, and the race guard
(unload firing mid-transcription must not fail the in-flight call).
The race guard is mutation-verified: reverting the fix (re-reading
the global at the call site) makes it fail with the exact NoneType
error; the fixed code passes.
2026-08-07 19:26:12 +05:30
kshitij 7b006ea6e8 feat(stt): idle unload for local whisper model
The local faster-whisper model singleton (_local_model) is loaded once
and never released — the 'base' model holds ~370 MB of RAM/VRAM for
the entire lifetime of the process, even when no voice messages arrive
for hours or days. On long-running gateway processes (especially with
local LLMs competing for the same GPU) this is wasteful.

Add a config-driven idle unload: after stt.local.unload_after_idle_seconds
(default 0 = never) of no transcription activity, a lightweight daemon
thread sets _local_model = None so the Python GC can reclaim the
ctranslate2 objects. The next voice message reloads the model
transparently (the existing lazy-load path handles it).

The watcher:
  - Checks every 30s whether idle time exceeds the configured threshold
  - Acquires _local_model_lock before unloading (prevents races with
    concurrent transcriptions that are mid-load)
  - Exits immediately if the model is already None (unloaded by another
    path, e.g. the CUDA fallback eviction)
  - Is restarted by each transcription with the current config value,
    so changing stt.local.unload_after_idle_seconds in config.yaml takes
    effect on the next voice message without a process restart

Default is 0 (never unload) — zero behavior change for existing users.
Recommended value for gateway processes: 300 (5 minutes).

15 tests: config resolution (garbage/negative/None fallbacks), unload
safety (already-None, lock acquisition), touch timestamp, watcher
lifecycle (unload after timeout, no unload within timeout, exits when
model already None, stopped on new start). Existing STT test suite
unchanged.
2026-08-07 19:26:12 +05:30
kshitij 3277eb8872 refactor(stt): fold review findings into the cloud silence trim
Three-reviewer pass (reuse / quality / efficiency) on the trim diff;
four findings folded:

1. Short-clip input gate (efficiency, HIGH): the trim previously paid
   the full ffmpeg encode before the <10%-saving discard check — every
   dense conversational voice note burned 3 subprocess spawns + a
   complete re-encode on the synchronous response path for nothing.
   New _CLOUD_TRIM_MIN_INPUT_SECONDS=12 gate: below it, savings can't
   matter (a >=10% saving is ~1s of audio, and several providers bill
   a per-request minimum anyway — Groq bills 10s minimum), so the
   whole pipeline is skipped using the duration we already probed.
   Typical 5-10s voice notes now pay 1 ffprobe (~50ms), not 3 spawns +
   encode (~0.3-1s; multi-second on small-VPS gateway hosts).

2. Shared encode profile (reuse, HIGH): the trim's ffmpeg command
   duplicated _transcode_audio_for_stt's encode byte-for-byte (same
   16kHz/mono/AAC-32k/faststart args, same subprocess.run kwargs).
   Extracted _STT_M4A_ENCODE_ARGS + _run_ffmpeg_stt_encode(ffmpeg,
   in, out, audio_filter=None); both call sites now share one owner,
   so codec/bitrate/timeout changes can't drift between the paths.

3. is_truthy_value for the enable flag (quality, MEDIUM): raw
   bool(cfg.get(...)) treated a YAML string "false" as enabled — the
   exact bug class utils.is_truthy_value (already imported, already
   used by is_stt_enabled and the xai/elevenlabs flags) exists for.

4. All-silence guard scales with keep_ms (quality, LOW): the fixed
   0.3s floor equals the default keep window, so an output consisting
   solely of one kept pause could pass as "speech"; now
   max(0.3, 2*keep_seconds).

Also: _probe_audio_duration docstring documents it as the canonical
sync seconds-probe (gateway/run.py and the Telegram adapter carry
local variants of the same ffprobe invocation).

Tests: 24 now — YAML-string-false disables; short clips skip the
encode entirely (encoder mock asserted not-called); E2E fixtures
moved past the input gate. E2E re-verified: 13.2s note -> 6.2s
(-53%), 8s clip skipped with 1 probe.
2026-08-07 19:26:04 +05:30
kshitij a683ef95d2 feat(stt): pre-upload silence trim for cloud providers
Local faster-whisper gets Silero VAD (bf8004e3a) so silence never
reaches the model. Cloud providers got no such protection: the raw
file uploads untouched, so every second of silence in a voice note is
paid for twice — upload time and per-audio-minute billing — and cloud
Whisper hallucinates junk tokens on silent stretches exactly like
local Whisper did before the VAD hardening. A 13s voice note with two
long pauses is billed as 13s of audio to transcribe ~6s of speech.

Close the gap client-side: before uploading to a built-in cloud
provider (groq/openai/mistral/xai/elevenlabs/deepinfra), collapse long
pauses with ffmpeg's silenceremove filter, keeping
stt.cloud_trim_keep_ms (default 300) of every pause so word boundaries
and natural pacing survive. Uses ffmpeg, already a dependency of this
exact path via _transcode_audio_for_stt — no new dependency.

The trim is strictly best-effort — ALL of these upload the original
untouched, transcription never fails because of the trim:
  - stt.cloud_trim_silence: false
  - ffmpeg/ffprobe missing, trim failure, or timeout
  - trimmed result ~empty (mostly-silence clip: the provider, not a
    client-side dB heuristic, decides whether it contains speech)
  - trim saves <10% (re-encoding for nothing)

Command-type and plugin providers are deliberately NOT trimmed: they
may wrap local CLIs that want the original bytes or run their own VAD.

E2E (real ffmpeg + faster-whisper): 13.2s voice note with 7s pause ->
6.2s upload (-53%); transcript of trimmed audio matches the original
on both utterances. Dense-speech and all-silence WAVs correctly fall
back to the original. 22 unit+E2E tests; STT/voice suite failures
identical to upstream/main baseline (all pre-existing).
2026-08-07 19:26:04 +05:30
PRATHAMESH75 fe6330de03 fix(stt): thread confidence thresholds into faster-whisper's own gate (#74178)
build_local_transcribe_kwargs read stt.local.no_speech_prob_threshold /
stt.local.logprob_threshold only for Hermes' post-filter
(_is_hallucinated_segment). faster-whisper's model.transcribe() never
received them, so its internal defaults (no_speech_threshold=0.6,
log_prob_threshold=-1.0) always applied and silently dropped
low-confidence segments before they reached the post-filter — making
those config knobs dead for the first gate.

Non-English speech decodes at a lower avg_logprob, so the English-tuned
defaults discard whole utterances (empty transcript despite correct
capture and language detection). Map the same config values through to
model.transcribe() so both gates stay in sync and the knobs work.
Defaults are unchanged, so behavior is identical unless a user tunes them.

Fixes #74178
2026-08-03 14:30:05 +05:30
Teknium bf8004e3a8 fix(stt): kill faster-whisper silence hallucinations at the source
Local faster-whisper called model.transcribe with bare {'beam_size': 5}:
no VAD, cross-window conditioning on, no confidence filtering. Pure
silence produced hallucinated tokens (E2E: 5s anullsrc WAV -> 'You',
no_speech_prob=0.705) and noisy clips could produce runs of junk, often
in other languages.

Three-layer class fix, one shared owner for every local-whisper call
site (build_local_transcribe_kwargs):

1. Silero VAD filter (bundled with faster-whisper) on by default —
   silence never reaches the model. stt.local.vad: false restores the
   raw behavior for music/ambient transcription.
   stt.local.vad_min_silence_ms tunes chunk splitting (default 500).
2. condition_on_previous_text=False — one hallucinated token can no
   longer seed a self-reinforcing run; negligible cost for
   voice-note-length audio.
3. Segment confidence gate (_join_confident_segments): drop a segment
   only when no_speech_prob > 0.6 AND avg_logprob < -1.0 (openai-whisper's
   own heuristic shape; both must hit so quiet-but-real speech survives).
   Config: stt.local.no_speech_prob_threshold / logprob_threshold.

The WHISPER_HALLUCINATIONS blocklist in voice_mode.py stays as
last-resort defense but should now almost never fire.

E2E (real faster-whisper 'base', CPU int8):
  silence.wav  before 'You'                        -> after ''
  noise.wav    before ''                           -> after ''
  speech.wav   before/after 'Hello World, this is a test of the
               transcription system.' (unchanged)

Docs (EN + zh-Hans), DEFAULT_CONFIG, cli-config.yaml.example updated;
19 unit tests (kwargs contract, off-switch, confidence gate incl.
quiet-speech survival, _transcribe_local wiring), sabotage-verified.
2026-07-29 00:01:33 -07:00
Teknium 805c1c340c feat(stt): support OpenAI gpt-transcribe transcription model
Adds gpt-transcribe (OpenAI's new file-transcription model, $0.0045/min)
to the OpenAI STT provider:

- OPENAI_MODELS set: gpt-transcribe is recognized so provider
  auto-correction keeps it on OpenAI and rejects it on Groq
- Language hint wiring: gpt-transcribe replaces the singular
  'language' field with a 'languages' list; the API rejects the legacy
  field, so the hint is sent via extra_body {languages: [..]}
- Config comment (DEFAULT_CONFIG), cli-config.yaml.example, desktop
  settings enum, and docs (en + zh-Hans) updated
- Tests: model pass-through, languages-list hint shape, legacy singular
  hint preserved for gpt-4o-transcribe, Groq auto-correction

gpt-live-transcribe (realtime WebSocket, $0.017/min) is NOT wired here:
the file-based STT pipeline has no realtime session path; it belongs in
a future realtime/voice-mode integration.
2026-07-28 22:40:45 -07:00
Teknium 050461ec83 fix: hoist STT credential read guard to the public transcribe_audio entry point
The rebase onto #73510's prepare/dispatch split left the guard inside
_transcribe_prepared_audio, where source validation ran first and a
blocked .env surfaced a format error instead of the read-block message.
Guard now fires before any validation/preprocessing.
2026-07-28 18:12:26 -07:00
Teknium e251e78df9 feat(tools): env_passthrough allowlist for command-provider secret scrub
Command providers legitimately reference their own API keys in shell
templates (curl one-liners). The #70342 scrub removes ALL provider keys,
which would break such setups. Add a per-provider env_passthrough list
(TTS + STT) that copies named variables back from the parent env, plus
docs and tests. Scrub stays the default; passthrough is explicit opt-in.
2026-07-28 18:12:26 -07:00
Teknium fc26e965bb fix(tools): apply idle timeout to command STT runner (class fix for #50081)
Port the progress-based idle-timeout pattern from _run_command_tts
(PR #50087, @CleanDev-Fix) to _run_command_stt: the timeout resets on
any stdout/stderr output, so a slow-but-alive STT provider survives
while a silently stalled one is killed. Stuck detection stays
progress-based, never wall-clock.
2026-07-28 18:12:26 -07:00
Frowtek 38bb193f38 fix(stt): route transcription inputs through the shared read guard
`transcribe_audio` reads a local file and hands it to the configured STT
provider — for the hosted providers (Groq, OpenAI, Mistral, xAI, ElevenLabs)
that ships the file's bytes to a third-party API. The same local-input read
guard was added to image-gen (587be5b5b) and xAI video-gen (104232979) to keep
the agent from feeding credential/secret stores to a provider, but STT was
missed.

Call `get_read_block_error(file_path)` at the top of `transcribe_audio`, before
validation/dispatch, so a `.env`, `auth.json`, `.anthropic_oauth.json`,
`mcp-tokens/`, etc. is refused up front instead of being transcribed (and, for
hosted providers, exfiltrated). This is defense-in-depth, not a security
boundary — the guard's own message says so — but it restores parity with the
image/video-gen tools.

Regression test: a `.env` file is refused with the shared read-guard message
before any provider dispatch (mutation-verified).
2026-07-28 18:12:26 -07:00
Eugeniusz Gilewski b76acacbb9 fix(tools): execute local STT templates without a shell
HERMES_LOCAL_STT_COMMAND rendered quoted placeholders into a
user-configured template and passed the result to shell=True. Shell
metacharacters in the template therefore remained executable syntax even
though the placeholder values themselves were quoted.

Tokenize the rendered template and invoke it as an argv list while
preserving the existing timeout, closed stdin, and Windows creation flags.
Lock the invocation contract with metacharacter regression coverage and
document explicit shell wrapping for trusted templates that need it.

Salvages #32694

Co-authored-by: Ernest Hysa <takis312@hotmail.com>
2026-07-28 18:12:26 -07:00
峯岸 亮 24a6fb6448 fix(security): scrub Hermes secrets from voice command subprocess env
Salvage incomplete #56332: route command TTS/STT through hermes_subprocess_env
while preserving delegated-child lineage, and close the sibling local-whisper
subprocess.run path that still inherited the full process environment.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-28 18:12:26 -07:00
Vivaan Dhawan 1c30c57f11 fix(whatsapp): preserve voice notes when STT fails 2026-07-28 14:06:56 -07:00
Florian Valade 5277065260 feat(stt): add CAF format support with WAV conversion for cloud providers
Cloud STT APIs (Groq, OpenAI, etc.) cannot parse Apple CAF containers.
Add .caf to SUPPORTED_FORMATS and _convert_caf_to_wav() which tries
ffmpeg (cross-platform) then afconvert (macOS built-in).
2026-07-28 14:06:56 -07:00
dsad f96eee3a91 fix(xai): pin oauth side-tool base URLs 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 c0c5dac531 fix: stdin=DEVNULL + windows_hide_flags on STT transcode subprocess (guard test) 2026-07-28 11:53:36 -07:00
Teknium d66ec2f5f4 fix: explicit utf-8 encoding on ffmpeg STT transcode subprocess (Windows footgun lint) 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
Carl Borg ef0d8ce2c5 transcription: transcode to m4a and retry when OpenAI STT rejects the audio container
Newer OpenAI transcription models (gpt-4o-transcribe, gpt-4o-mini-transcribe)
reject some containers the legacy whisper-1 endpoint accepted -- notably the
Ogg/Opus voice notes messaging platforms deliver -- returning a 400
'corrupted or unsupported' error, so voice-note transcription fails for users
on those models even though SUPPORTED_FORMATS still advertises .ogg/.aac/.flac.

Wrap the OpenAI upload: on a format-related BadRequestError, transcode the
source to a compact 16 kHz mono AAC .m4a via ffmpeg and retry once. This is
model-agnostic (no per-model format table to maintain) and adds no cost for
formats the endpoint already accepts.

Fixes #68719
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
Damian Kluk a784e74c7f fix(stt): better error logging when faster-whisper lazy install fails
Log lazy-install failures at WARNING instead of DEBUG, with actionable
guidance about venv write-permission issues (the most common cause of
silent STT failures).

Salvaged from PR #46127 (transcription_tools half only — the gateway DM
hunks are superseded by main's neutral-marker enrichment design, and the
Docker/CI files were unrelated scope).

(cherry picked from commit d3e07bdaaa, reduced)
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
Tushar a6a439338e fix(tts): fall through to raw import when lazy_deps fails (#53259)
Replace 
aise ImportError(str(e)) with pass in the except Exception
handler of _import_edge_tts(), _import_elevenlabs(), and
_import_mistral_client() so packages installed via PYTHONPATH or Docker
layered filesystems still work when lazy_deps.ensure() raises.

Also fix the Mistral STT path in transcription_tools.py which only
caught ImportError, not FeatureUnavailable.

Adds 6 regression tests using sys.modules fixtures (no
builtins.__import__ patching).
2026-07-28 11:53:36 -07:00
Charles Cha ffc3ce27d5 fix(stt): scope upload size limits to remote providers 2026-07-28 11:53:36 -07:00
AnthonyAssistantAi 884900ffd6 fix: avoid local STT crash on Apple Silicon
Force CPU (int8) for faster-whisper on Apple Silicon / Rosetta, where
ctranslate2's device=auto path can hard-abort in native code. Salvaged
from PR #28624 without the numpy pin change (main already moved on).

(cherry picked from commit 7edf2d5196, pyproject.toml hunk dropped)
2026-07-28 11:53:36 -07:00
liuhao1024 766e856118 fix(stt): treat CUBLAS_STATUS_NOT_SUPPORTED as CUDA lib error
- Add Blackwell-specific cuBLAS error marker to _CUDA_LIB_ERROR_MARKERS
- Allows CPU fallback on RTX 5090 (sm_120) when faster-whisper
  reports CUBLAS_STATUS_NOT_SUPPORTED instead of loading successfully
- Add regression test for CUBLAS_STATUS_NOT_SUPPORTED path

Closes #17526
2026-07-28 11:53:36 -07:00
Tranquil-Flow 06fc6e0c29 fix(stt): respect device and compute_type from config.yaml
The local STT transcription function hardcoded device="auto" and
compute_type="auto" when instantiating WhisperModel, ignoring the
user's stt.local.device and stt.local.compute_type config values.

Closes #8319
2026-07-28 11:53:36 -07:00
Teknium c136400c9e fix(voice): single scoped resolver — STT/TTS keys fall back to the credential pool
Rework of #68509 per triage: hoist the duplicated per-tool
_resolve_provider_key helpers into one owner,
tools.tool_backend_helpers.resolve_provider_secret(), and migrate every
STT/TTS key lookup site to it.

Resolution order: explicit config.yaml value > profile secret scope /
env / ~/.hermes/.env > credential pool (checks both '<provider>' and
'custom:<provider>' pool keys, so keys added via 'hermes auth add
mistral' or declared under providers.<name> both resolve). Under an
active multiplex turn the profile scope stays authoritative — no pool
or .env fallback that could borrow another profile's key (composes with
the #69469 scope fix).

Coverage now includes GROQ_API_KEY, MISTRAL_API_KEY, ELEVENLABS_API_KEY,
DEEPINFRA_API_KEY, MINIMAX_API_KEY, GEMINI_API_KEY/GOOGLE_API_KEY, the
XAI_API_KEY fallback in resolve_xai_http_credentials, and the OpenAI
audio key (resolve_openai_audio_api_key now pool-aware for
OPENAI_API_KEY via 'hermes auth add openai-api').

Unit tests: fake pool entry proves each provider resolves from the pool
when env is empty; env still wins when set; config wins over both; a
multiplex scope miss never borrows the pool; pool read failures never
raise; tool-level wiring for STT, TTS, xAI, and OpenAI audio.

Fixes #68003
2026-07-28 11:53:09 -07:00
sg-architect 79bcfc23ab fix(tools): resolve TTS/STT provider keys through credential pool
TTS/STT providers (Mistral, ElevenLabs) only checked env vars and
.env files via get_env_value(), ignoring keys stored via
'hermes auth add mistral' / 'hermes auth add elevenlabs'.

Add _resolve_provider_key() helper that falls back to the credential
pool (agent.credential_pool.load_pool) when the env var is unset.

Affected:
- tools/transcription_tools.py: 6 sites (provider selection + auto-detect)
- tools/tts_tool.py: 6 sites (synthesis + availability check)

Fixes #68003
2026-07-28 11:53:09 -07:00