From 79bcfc23abeb4df3e6b8826f4c3960bb50d72a17 Mon Sep 17 00:00:00 2001 From: sg-architect Date: Tue, 21 Jul 2026 16:50:13 +0800 Subject: [PATCH] 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 --- tools/transcription_tools.py | 36 ++++++++++++++++++++++++++++++------ tools/tts_tool.py | 34 +++++++++++++++++++++++++++++----- 2 files changed, 59 insertions(+), 11 deletions(-) diff --git a/tools/transcription_tools.py b/tools/transcription_tools.py index de77b836260e..15cad62152c9 100644 --- a/tools/transcription_tools.py +++ b/tools/transcription_tools.py @@ -62,6 +62,30 @@ def get_env_value(name, default=None): value = _get_env_value(name) return default if value is None else value + +def _resolve_provider_key(env_var: str, provider_id: str) -> str: + """Resolve an API key from env, .env, or the credential pool. + + Used by TTS/STT providers (Mistral, ElevenLabs) that store keys + via ``hermes auth add ``. + """ + key = get_env_value(env_var) + if key: + return key + try: + from agent.credential_pool import load_pool + pool = load_pool(provider_id) + if pool and pool.has_credentials(): + entry = pool.peek() + if entry: + key = getattr(entry, "access_token", "") or getattr(entry, "runtime_api_key", "") + key = str(key).strip() + if key: + return key + except Exception: + pass + return "" + # --------------------------------------------------------------------------- # Optional imports — graceful degradation # --------------------------------------------------------------------------- @@ -843,7 +867,7 @@ def _get_provider(stt_config: dict) -> str: return "none" if provider == "mistral": - if _HAS_MISTRAL and get_env_value("MISTRAL_API_KEY"): + if _HAS_MISTRAL and _resolve_provider_key("MISTRAL_API_KEY", "mistral"): return "mistral" logger.warning( "STT provider 'mistral' configured but mistralai package " @@ -862,7 +886,7 @@ def _get_provider(stt_config: dict) -> str: return "none" if provider == "elevenlabs": - if get_env_value("ELEVENLABS_API_KEY"): + if _resolve_provider_key("ELEVENLABS_API_KEY", "elevenlabs"): return "elevenlabs" logger.warning( "STT provider 'elevenlabs' configured but ELEVENLABS_API_KEY not set" @@ -904,7 +928,7 @@ def _get_provider(stt_config: dict) -> str: # Only auto-select Mistral if the SDK is already present — don't trigger a # lazy-install during passive auto-detection. Explicit `provider: mistral` # (above) does lazy-install on first transcription call. - if _HAS_MISTRAL and get_env_value("MISTRAL_API_KEY"): + if _HAS_MISTRAL and _resolve_provider_key("MISTRAL_API_KEY", "mistral"): logger.info("No local STT available, using Mistral Voxtral Transcribe API") return "mistral" try: @@ -915,7 +939,7 @@ def _get_provider(stt_config: dict) -> str: return "xai" except Exception: pass - if get_env_value("ELEVENLABS_API_KEY"): + if _resolve_provider_key("ELEVENLABS_API_KEY", "elevenlabs"): logger.info("No local STT available, using ElevenLabs Scribe STT API") return "elevenlabs" if _HAS_OPENAI and (get_env_value("DEEPINFRA_API_KEY") or "").strip(): @@ -1483,7 +1507,7 @@ def _transcribe_mistral(file_path: str, model_name: str) -> Dict[str, Any]: Uses the ``mistralai`` Python SDK to call ``/v1/audio/transcriptions``. Requires ``MISTRAL_API_KEY`` environment variable. """ - api_key = get_env_value("MISTRAL_API_KEY") + api_key = _resolve_provider_key("MISTRAL_API_KEY", "mistral") if not api_key: return {"success": False, "transcript": "", "error": "MISTRAL_API_KEY not set"} @@ -1632,7 +1656,7 @@ def _transcribe_xai(file_path: str, model_name: str) -> Dict[str, Any]: def _transcribe_elevenlabs(file_path: str, model_name: str) -> Dict[str, Any]: """Transcribe using ElevenLabs Scribe STT API.""" - api_key = get_env_value("ELEVENLABS_API_KEY") + api_key = _resolve_provider_key("ELEVENLABS_API_KEY", "elevenlabs") if not api_key: return {"success": False, "transcript": "", "error": "ELEVENLABS_API_KEY not set"} diff --git a/tools/tts_tool.py b/tools/tts_tool.py index 556c3fe6bbee..66ab9127f586 100644 --- a/tools/tts_tool.py +++ b/tools/tts_tool.py @@ -69,6 +69,30 @@ def get_env_value(name, default=None): return os.getenv(name, default) value = _get_env_value(name) return default if value is None else value + + +def _resolve_provider_key(env_var: str, provider_id: str) -> str: + """Resolve an API key from env, .env, or the credential pool. + + Used by TTS providers (Mistral, ElevenLabs) that store keys + via ``hermes auth add ``. + """ + key = get_env_value(env_var) + if key: + return key + try: + from agent.credential_pool import load_pool + pool = load_pool(provider_id) + if pool and pool.has_credentials(): + entry = pool.peek() + if entry: + key = getattr(entry, "access_token", "") or getattr(entry, "runtime_api_key", "") + key = str(key).strip() + if key: + return key + except Exception: + pass + return "" from tools.managed_tool_gateway import resolve_managed_tool_gateway from tools.tool_backend_helpers import ( managed_nous_tools_enabled, @@ -1092,7 +1116,7 @@ def _generate_elevenlabs(text: str, output_path: str, tts_config: Dict[str, Any] Returns: Path to the saved audio file. """ - api_key = (get_env_value("ELEVENLABS_API_KEY") or "") + api_key = (_resolve_provider_key("ELEVENLABS_API_KEY", "elevenlabs") or "") if not api_key: raise ValueError("ELEVENLABS_API_KEY not set. Get one at https://elevenlabs.io/") @@ -1663,7 +1687,7 @@ def _generate_mistral_tts(text: str, output_path: str, tts_config: Dict[str, Any and writes the raw bytes to *output_path*. Supports native Opus output for Telegram voice bubbles. """ - api_key = (get_env_value("MISTRAL_API_KEY") or "") + api_key = (_resolve_provider_key("MISTRAL_API_KEY", "mistral") or "") if not api_key: raise ValueError("MISTRAL_API_KEY not set. Get one at https://console.mistral.ai/") @@ -2746,7 +2770,7 @@ def check_tts_requirements() -> bool: _import_elevenlabs() except ImportError: return False - return bool(get_env_value("ELEVENLABS_API_KEY")) + return bool(_resolve_provider_key("ELEVENLABS_API_KEY", "elevenlabs")) if provider == "openai": try: _import_openai_client() @@ -2775,7 +2799,7 @@ def check_tts_requirements() -> bool: _import_mistral_client() except ImportError: return False - return bool(get_env_value("MISTRAL_API_KEY")) + return bool(_resolve_provider_key("MISTRAL_API_KEY", "mistral")) if provider == "neutts": return _check_neutts_available() if provider == "kittentts": @@ -3074,7 +3098,7 @@ if __name__ == "__main__": print("\nProvider availability:") print(f" Edge TTS: {'installed' if _check(_import_edge_tts, 'edge') else 'not installed (pip install edge-tts)'}") print(f" ElevenLabs: {'installed' if _check(_import_elevenlabs, 'el') else 'not installed (pip install elevenlabs)'}") - print(f" API Key: {'set' if get_env_value('ELEVENLABS_API_KEY') else 'not set'}") + print(f" API Key: {'set' if _resolve_provider_key('ELEVENLABS_API_KEY', 'elevenlabs') else 'not set'}") print(f" OpenAI: {'installed' if _check(_import_openai_client, 'oai') else 'not installed'}") print( " API Key: "