{"repo":"NousResearch/hermes-agent","public_id":"5887","display_label":"Adding Mistral Voxtral as STT provider","summary":"These items concern adding Voxtral Transcribe (Mistral AI) as a speech-to-text provider, including an interim fix related to a mistralai package ban and its subsequent restoration.","subsystem":"skills","counts":{"members":3,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":6284,"merged_at":"2026-04-08T20:47:08+00:00"},"resolution":{"state":"unresolved","by":null},"freshness":{"state":"stale","last_verified_at":"2026-04-08T20:47:08+00:00","is_stale":true},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.6666666666666666},"fix_edges":[{"issue_number":5887,"pr_number":6284,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false,"pr_merged_at":"2026-04-08T20:47:08+00:00"}],"members":[{"kind":"issue","number":5887,"title":"Add Mistral (Voxtral Transcribe) as STT provider","url":null,"state":"closed"},{"kind":"pr","number":5573,"title":"feat(tools): add Voxtral Transcribe STT provider (Mistral AI)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#6284","number":6284}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":6284,"title":"feat(tools): add Voxtral Transcribe STT provider (Mistral AI)","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["cli-config.yaml.example","hermes_cli/config.py","pyproject.toml","tests/tools/test_transcription_tools.py","tools/transcription_tools.py","website/docs/user-guide/features/tts.md"],"fix_hunks":[{"file":"cli-config.yaml.example","hunks":["@@ -644,10 +644,14 @@ platform_toolsets:\n # Voice Transcription (Speech-to-Text)\n # =============================================================================\n # Automatically transcribe voice messages on messaging platforms.\n-# Requires OPENAI_API_KEY in .env (uses OpenAI Whisper API directly).\n+# Providers: local (free, faster-whisper) | groq (free tier) | openai (Whisper API) | mistral (Voxtral Transcribe)\n+# Set the corresponding API key in .env: GROQ_API_KEY, OPENAI_API_KEY, or MISTRAL_API_KEY.\n stt:\n   enabled: true\n+  # provider: \"local\"          # auto-detected if omitted\n   model: \"whisper-1\"  # whisper-1 (cheapest) | gpt-4o-mini-transcribe | gpt-4o-transcribe\n+  # mistral:\n+  #   model: \"voxtral-mini-latest\"  # voxtral-mini-latest | voxtral-mini-2602\n \n # =============================================================================\n # Response Pacing (Messaging Platforms)"]},{"file":"hermes_cli/config.py","hunks":["@@ -413,7 +413,7 @@ def ensure_hermes_home():\n     \n     \"stt\": {\n         \"enabled\": True,\n-        \"provider\": \"local\",  # \"local\" (free, faster-whisper) | \"groq\" | \"openai\" (Whisper API)\n+        \"provider\": \"local\",  # \"local\" (free, faster-whisper) | \"groq\" | \"openai\" (Whisper API) | \"mistral\" (Voxtral Transcribe)\n         \"local\": {\n             \"model\": \"base\",  # tiny, base, small, medium, large-v3\n             \"language\": \"\",  # auto-detect by default; set to \"en\", \"es\", \"fr\", etc. to force","@@ -421,6 +421,9 @@ def ensure_hermes_home():\n         \"openai\": {\n             \"model\": \"whisper-1\",  # whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe\n         },\n+        \"mistral\": {\n+            \"model\": \"voxtral-mini-latest\",  # voxtral-mini-latest, voxtral-mini-2602\n+        },\n     },\n \n     \"voice\": {"]},{"file":"pyproject.toml","hunks":["@@ -62,6 +62,7 @@ mcp = [\"mcp>=1.2.0,<2\"]\n homeassistant = [\"aiohttp>=3.9.0,<4\"]\n sms = [\"aiohttp>=3.9.0,<4\"]\n acp = [\"agent-client-protocol>=0.9.0,<1.0\"]\n+mistral = [\"mistralai>=2.3.0,<3\"]\n dingtalk = [\"dingtalk-stream>=0.1.0,<1\"]\n feishu = [\"lark-oapi>=1.5.3,<2\"]\n rl = [","@@ -94,6 +95,7 @@ all = [\n   \"hermes-agent[voice]\",\n   \"hermes-agent[dingtalk]\",\n   \"hermes-agent[feishu]\",\n+  \"hermes-agent[mistral]\",\n ]\n \n [project.scripts]"]},{"file":"tests/tools/test_transcription_tools.py","hunks":["@@ -48,6 +48,7 @@ def clean_env(monkeypatch):\n     monkeypatch.delenv(\"VOICE_TOOLS_OPENAI_KEY\", raising=False)\n     monkeypatch.delenv(\"OPENAI_API_KEY\", raising=False)\n     monkeypatch.delenv(\"GROQ_API_KEY\", raising=False)\n+    monkeypatch.delenv(\"MISTRAL_API_KEY\", raising=False)\n     monkeypatch.delenv(\"HERMES_LOCAL_STT_COMMAND\", raising=False)\n     monkeypatch.delenv(\"HERMES_LOCAL_STT_LANGUAGE\", raising=False)\n ","@@ -858,3 +859,183 @@ def test_returns_none_when_model_key_missing(self, tmp_path, monkeypatch):\n \n         from tools.transcription_tools import get_stt_model_from_config\n         assert get_stt_model_from_config() is None\n+\n+\n+# ============================================================================\n+# _transcribe_mistral\n+# ============================================================================\n+\n+\n[redacted:email]\n+def mock_mistral_module():\n+    \"\"\"Inject a fake mistralai module into sys.modules for testing.\"\"\"\n+    mock_client = MagicMock()\n+    mock_client.__enter__ = MagicMock(return_value=mock_client)\n+    mock_client.__exit__ = MagicMock(return_value=False)\n+    mock_mistral_cls = MagicMock(return_value=mock_client)\n+    fake_module = MagicMock()\n+    fake_module.Mistral = mock_mistral_cls\n+    with patch.dict(\"sys.modules\", {\"mistralai\": fake_module, \"mistralai.client\": fake_module}):\n+        yield mock_client\n+\n+\n+class TestTranscribeMistral:\n+    def test_no_key(self, monkeypatch):\n+        monkeypatch.delenv(\"MISTRAL_API_KEY\", raising=False)\n+        from tools.transcription_tools import _transcribe_mistral\n+        result = _transcribe_mistral(\"/tmp/test.ogg\", \"voxtral-mini-latest\")\n+        assert result[\"success\"] is False\n+        assert \"MISTRAL_API_KEY\" in result[\"error\"]\n+\n+    def test_successful_transcription(self, monkeypatch, sample_ogg, mock_mistral_module):\n+        monkeypatch.setenv(\"MISTRAL_API_KEY\", \"test-key\")\n+\n+        mock_result = MagicMock()\n+        mock_result.text = \"hello from mistral\"\n+        mock_mistral_module.audio.transcriptions.complete.return_value = mock_result\n+\n+        from tools.transcription_tools import _transcribe_mistral\n+        result = _transcribe_mistral(sample_ogg, \"voxtral-mini-latest\")\n+\n+        assert result[\"success\"] is True\n+        assert result[\"transcript\"] == \"hello from mistral\"\n+        assert result[\"provider\"] == \"mistral\"\n+        mock_mistral_module.audio.transcriptions.complete.assert_called_once()\n+        mock_mistral_module.__exit__.assert_called_once()\n+\n+    def test_api_error_returns_failure(self, monkeypatch, sample_ogg, mock_mistral_module):\n+        monkeypatch.setenv(\"MISTRAL_API_KEY\", \"test-key\")\n+        mock_mistral_module.audio.transcriptions.complete.side_effect = RuntimeError(\"secret-key-leaked\")\n+\n+        from tools.transcription_tools import _transcribe_mistral\n+        result = _transcribe_mistral(sample_ogg, \"voxtral-mini-latest\")\n+\n+        assert result[\"success\"] is False\n+        assert \"RuntimeError\" in result[\"error\"]\n+        assert \"secret-key-leaked\" not in result[\"error\"]\n+\n+    def test_permission_error(self, monkeypatch, sample_ogg, mock_mistral_module):\n+        monkeypatch.setenv(\"MISTRAL_API_KEY\", \"test-key\")\n+        mock_mistral_module.audio.transcriptions.complete.side_effect = PermissionError(\"denied\")\n+\n+        from tools.transcription_tools import _transcribe_mistral\n+        result = _transcribe_mistral(sample_ogg, \"voxtral-mini-latest\")\n+\n+        assert result[\"success\"] is False\n+        assert \"Permission denied\" in result[\"error\"]\n+\n+\n+# ============================================================================\n+# _get_provider — Mistral\n+# ============================================================================\n+\n+class TestGetProviderMistral:\n+    \"\"\"Mistral-specific provider selection tests.\"\"\"\n+\n+    def test_mistral_when_key_and_sdk_available(self, monkeypatch):\n+        monkeypatch.setenv(\"MISTRAL_API_KEY\", \"test-key\")\n+        with patch(\"tools.transcription_tools._HAS_MISTRAL\", True):\n+            from tools.transcription_tools import _get_provider\n+            assert _get_provider({\"provider\": \"mistral\"}) == \"mistral\"\n+\n+    def test_mistral_explicit_no_key_returns_none(self, monkeypatch):\n+        \"\"\"Explicit mistral with no key returns none — no cross-provider fallback.\"\"\"\n+        monkeypatch.delenv(\"MISTRAL_API_KEY\", raising=False)\n+        with patch(\"tools.transcription_tools._HAS_MISTRAL\", True):\n+            from tools.transcription_tools import _get_provider\n+            assert _get_provider({\"provider\": \"mistral\"}) == \"none\"\n+\n+    def test_mistral_explicit_no_sdk_returns_none(self, monkeypatch):\n+        \"\"\"Explicit mistral with key but no SDK returns none.\"\"\"\n+        monkeypatch.setenv(\"MISTRAL_API_KEY\", \"test-key\")\n+        with patch(\"tools.transcription_tools._HAS_MISTRAL\", False):\n+            from tools.transcription_tools import _get_provider\n+            assert _get_provider({\"provider\": \"mistral\"}) == \"none\"\n+\n+    def test_auto_detect_mistral_after_openai(self, monkeypatch):\n+        \"\"\"Auto-detect: mistral is tried after openai when both are unavailable.\"\"\"\n+        monkeypatch.delenv(\"GROQ_API_KEY\", raising=False)\n+        monkeypatch.delenv(\"VOICE_TOOLS_OPENAI_KEY\", raising=False)\n+        monkeypatch.delenv(\"OPENAI_API_KEY\", raising=False)\n+        monkeypatch.setenv(\"MISTRAL_API_KEY\", \"test-key\")\n+        with patch(\"tools.transcription_tools._HAS_FASTER_WHISPER\", False), \\\n+             patch(\"tools.transcription_tools._has_local_command\", return_value=False), \\\n+             patch(\"tools.transcription_tools._HAS_OPENAI\", False), \\\n+             patch(\"tools.transcription_tools._HAS_MISTRAL\", True):\n+            from tools.transcription_tools import _get_provider\n+            assert _get_provider({}) == \"mistral\"\n+\n+    def test_auto_detect_openai_preferred_over_mistral(self, monkeypatch):\n+        \"\"\"Auto-detect: openai is preferred over mistral (both paid, openai more common).\"\"\"\n+        monkeypatch.setenv(\"VOICE_TOOLS_OPENAI_KEY\", \"sk-test\")\n+        monkeypatch.setenv(\"MISTRAL_API_KEY\", \"test-key\")\n+        monkeypatch.delenv(\"GROQ_API_KEY\", raising=False)\n+        with patch(\"tools.transcription_tools._HAS_FASTER_WHISPER\", False), \\\n+             patch(\"tools.transcription_tools._has_local_command\", return_value=False), \\\n+             patch(\"tools.transcription_tools._HAS_OPENAI\", True), \\\n+             patch(\"tools.transcription_tools._HAS_MISTRAL\", True):\n+            from tools.transcription_tools import _get_provider\n+            assert _get_provider({}) == \"openai\"\n+\n+    def test_auto_detect_groq_preferred_over_mistral(self, monkeypatch):\n+        \"\"\"Auto-detect: groq (free) is preferred over mistral (paid).\"\"\"\n+        monkeypatch.setenv(\"GROQ_API_KEY\", \"gsk-test\")\n+        monkeypatch.setenv(\"MISTRAL_API_KEY\", \"test-key\")\n+        with patch(\"tools.transcription_tools._HAS_FASTER_WHISPER\", False), \\\n+             patch(\"tools.transcription_tools._has_local_command\", return_value=False), \\\n+             patch(\"tools.transcription_tools._HAS_OPENAI\", True), \\\n+             patch(\"tools.transcription_tools._HAS_MISTRAL\", True):\n+            from tools.transcription_tools import _get_provider\n+            assert _get_provider({}) == \"groq\"\n+\n+    def test_auto_detect_skips_mistral_without_sdk(self, monkeypatch):\n+        \"\"\"Auto-detect: mistral skipped when key is set but SDK is not installed.\"\"\"\n+        monkeypatch.delenv(\"GROQ_API_KEY\", raising=False)\n+        monkeypatch.delenv(\"VOICE_TOOLS_OPENAI_KEY\", raising=False)\n+        monkeypatch.delenv(\"OPENAI_API_KEY\", raising=False)\n+        monkeypatch.setenv(\"MISTRAL_API_KEY\", \"test-key\")\n+        with patch(\"tools.transcription_tools._HAS_FASTER_WHISPER\", False), \\\n+             patch(\"tools.transcription_tools._has_local_command\", return_value=False), \\\n+             patch(\"tools.transcription_tools._HAS_OPENAI\", False), \\\n+             patch(\"tools.transcription_tools._HAS_MISTRAL\", False):\n+            from tools.transcription_tools import _get_provider\n+            assert _get_provider({}) == \"none\"\n+\n+\n+# ============================================================================\n+# transcribe_audio — Mistral dispatch\n+# ============================================================================\n+\n+class TestTranscribeAudioMistralDispatch:\n+    def test_dispatches_to_mistral(self, sample_ogg):\n+        with patch(\"tools.transcription_tools._load_stt_config\", return_value={\"provider\": \"mistral\"}), \\\n+             patch(\"tools.transcription_tools._get_provider\", return_value=\"mistral\"), \\\n+             patch(\"tools.transcription_tools._transcribe_mistral\",\n+                   return_value={\"success\": True, \"transcript\": \"hi\", \"provider\": \"mistral\"}) as mock_mistral:\n+            from tools.transcription_tools import transcribe_audio\n+            result = transcribe_audio(sample_ogg)\n+\n+        assert result[\"success\"] is True\n+        assert result[\"provider\"] == \"mistral\"\n+        mock_mistral.assert_called_once()\n+\n+    def test_config_mistral_model_used(self, sample_ogg):\n+        config = {\"provider\": \"mistral\", \"mistral\": {\"model\": \"voxtral-mini-2602\"}}\n+        with patch(\"tools.transcription_tools._load_stt_config\", return_value=config), \\\n+             patch(\"tools.transcription_tools._get_provider\", return_value=\"mistral\"), \\\n+             patch(\"tools.transcription_tools._transcribe_mistral\",\n+                   return_value={\"success\": True, \"transcript\": \"hi\"}) as mock_mistral:\n+            from tools.transcription_tools import transcribe_audio\n+            transcribe_audio(sample_ogg, model=None)\n+\n+        assert mock_mistral.call_args[0][1] == \"voxtral-mini-2602\"\n+\n+    def test_model_override_passed_to_mistral(self, sample_ogg):\n+        with patch(\"tools.transcription_tools._load_stt_config\", return_value={}), \\\n+             patch(\"tools.transcription_tools._get_provider\", return_value=\"mistral\"), \\\n+             patch(\"tools.transcription_tools._transcribe_mistral\",\n+                   return_value={\"success\": True, \"transcript\": \"hi\"}) as mock_mistral:\n+            from tools.transcription_tools import transcribe_audio\n+            transcribe_audio(sample_ogg, model=\"voxtral-mini-2602\")\n+\n+        assert mock_mistral.call_args[0][1] == \"voxtral-mini-2602\""]},{"file":"tools/transcription_tools.py","hunks":["@@ -57,6 +57,7 @@ def _safe_find_spec(module_name: str) -> bool:\n \n _HAS_FASTER_WHISPER = _safe_find_spec(\"faster_whisper\")\n _HAS_OPENAI = _safe_find_spec(\"openai\")\n+_HAS_MISTRAL = _safe_find_spec(\"mistralai\")\n \n # ---------------------------------------------------------------------------\n # Constants","@@ -67,6 +68,7 @@ def _safe_find_spec(module_name: str) -> bool:\n DEFAULT_LOCAL_STT_LANGUAGE = \"en\"\n DEFAULT_STT_MODEL = os.getenv(\"STT_OPENAI_MODEL\", \"whisper-1\")\n DEFAULT_GROQ_STT_MODEL = os.getenv(\"STT_GROQ_MODEL\", \"whisper-large-v3-turbo\")\n+DEFAULT_MISTRAL_STT_MODEL = os.getenv(\"STT_MISTRAL_MODEL\", \"voxtral-mini-latest\")\n LOCAL_STT_COMMAND_ENV = \"HERMES_LOCAL_STT_COMMAND\"\n LOCAL_STT_LANGUAGE_ENV = \"HERMES_LOCAL_STT_LANGUAGE\"\n COMMON_LOCAL_BIN_DIRS = (\"/opt/homebrew/bin\", \"/usr/local/bin\")","@@ -74,7 +76,7 @@ def _safe_find_spec(module_name: str) -> bool:\n GROQ_BASE_URL = os.getenv(\"GROQ_BASE_URL\", \"https://api.groq.com/openai/v1\")\n OPENAI_BASE_URL = os.getenv(\"STT_OPENAI_BASE_URL\", \"https://api.openai.com/v1\")\n \n-SUPPORTED_FORMATS = {\".mp3\", \".mp4\", \".mpeg\", \".mpga\", \".m4a\", \".wav\", \".webm\", \".ogg\", \".aac\"}\n+SUPPORTED_FORMATS = {\".mp3\", \".mp4\", \".mpeg\", \".mpga\", \".m4a\", \".wav\", \".webm\", \".ogg\", \".aac\", \".flac\"}\n LOCAL_NATIVE_AUDIO_FORMATS = {\".wav\", \".aiff\", \".aif\"}\n MAX_FILE_SIZE = 25 * 1024 * 1024  # 25 MB\n ","@@ -227,9 +229,18 @@ def _get_provider(stt_config: dict) -> str:\n             )\n             return \"none\"\n \n+        if provider == \"mistral\":\n+            if _HAS_MISTRAL and os.getenv(\"MISTRAL_API_KEY\"):\n+                return \"mistral\"\n+            logger.warning(\n+                \"STT provider 'mistral' configured but mistralai package \"\n+                \"not installed or MISTRAL_API_KEY not set\"\n+            )\n+            return \"none\"\n+\n         return provider  # Unknown — let it fail downstream\n \n-    # --- Auto-detect (no explicit provider): local > groq > openai ---------\n+    # --- Auto-detect (no explicit provider): local > groq > openai > mistral -\n \n     if _HAS_FASTER_WHISPER:\n         return \"local\"","@@ -241,6 +252,9 @@ def _get_provider(stt_config: dict) -> str:\n     if _HAS_OPENAI and _has_openai_audio_backend():\n         logger.info(\"No local STT available, using OpenAI Whisper API\")\n         return \"openai\"\n+    if _HAS_MISTRAL and os.getenv(\"MISTRAL_API_KEY\"):\n+        logger.info(\"No local STT available, using Mistral Voxtral Transcribe API\")\n+        return \"mistral\"\n     return \"none\"\n \n # ---------------------------------------------------------------------------","@@ -516,6 +530,45 @@ def _transcribe_openai(file_path: str, model_name: str) -> Dict[str, Any]:\n         logger.error(\"OpenAI transcription failed: %s\", e, exc_info=True)\n         return {\"success\": False, \"transcript\": \"\", \"error\": f\"Transcription failed: {e}\"}\n \n+# ---------------------------------------------------------------------------\n+# Provider: mistral (Voxtral Transcribe API)\n+# ---------------------------------------------------------------------------\n+\n+\n+def _transcribe_mistral(file_path: str, model_name: str) -> Dict[str, Any]:\n+    \"\"\"Transcribe using Mistral Voxtral Transcribe API.\n+\n+    Uses the ``mistralai`` Python SDK to call ``/v1/audio/transcriptions``.\n+    Requires ``MISTRAL_API_KEY`` environment variable.\n+    \"\"\"\n+    api_key = os.getenv(\"MISTRAL_API_KEY\")\n+    if not api_key:\n+        return {\"success\": False, \"transcript\": \"\", \"error\": \"MISTRAL_API_KEY not set\"}\n+\n+    try:\n+        from mistralai.client import Mistral\n+\n+        with Mistral(api_key=[redacted:secret-assignment] as client:\n+            with open(file_path, \"rb\") as audio_file:\n+                result = client.audio.transcriptions.complete(\n+                    model=model_name,\n+                    file={\"content\": audio_file, \"file_name\": Path(file_path).name},\n+                )\n+\n+            transcript_text = _extract_transcript_text(result)\n+            logger.info(\n+                \"Transcribed %s via Mistral API (%s, %d chars)\",\n+                Path(file_path).name, model_name, len(transcript_text),\n+            )\n+            return {\"success\": True, \"transcript\": transcript_text, \"provider\": \"mistral\"}\n+\n+    except PermissionError:\n+        return {\"success\": False, \"transcript\": \"\", \"error\": f\"Permission denied: {file_path}\"}\n+    except Exception as e:\n+        logger.error(\"Mistral transcription failed: %s\", e, exc_info=True)\n+        return {\"success\": False, \"transcript\": \"\", \"error\": f\"Mistral transcription failed: {type(e).__name__}\"}\n+\n+\n # ---------------------------------------------------------------------------\n # Public API\n # ---------------------------------------------------------------------------","@@ -577,6 +630,11 @@ def transcribe_audio(file_path: str, model: Optional[str] = None) -> Dict[str, A\n         model_name = model or openai_cfg.get(\"model\", DEFAULT_STT_MODEL)\n         return _transcribe_openai(file_path, model_name)\n \n+    if provider == \"mistral\":\n+        mistral_cfg = stt_config.get(\"mistral\", {})\n+        model_name = model or mistral_cfg.get(\"model\", DEFAULT_MISTRAL_STT_MODEL)\n+        return _transcribe_mistral(file_path, model_name)\n+\n     # No provider available\n     return {\n         \"success\": False,","@@ -584,7 +642,8 @@ def transcribe_audio(file_path: str, model: Optional[str] = None) -> Dict[str, A\n         \"error\": (\n             \"No STT provider available. Install faster-whisper for free local \"\n             f\"transcription, configure {LOCAL_STT_COMMAND_ENV} or install a local whisper CLI, \"\n-            \"set GROQ_API_KEY for free Groq Whisper, or set VOICE_TOOLS_OPENAI_KEY \"\n+            \"set GROQ_API_KEY for free Groq Whisper, set MISTRAL_API_KEY for Mistral \"\n+            \"Voxtral Transcribe, or set VOICE_TOOLS_OPENAI_KEY \"\n             \"or OPENAI_API_KEY for the OpenAI Whisper API.\"\n         ),\n     }"]},{"file":"uv.lock","hunks":["@@ -1158,6 +1158,15 @@ wheels = [\n     { url = \"https://files.pythonhosted.org/packages/97/a8/c070e1340636acb38d4e6a7e45c46d168a462b48b9b3257e14ca0e5af79b/environs-14.6.0-py3-none-any.whl\", hash = \"sha256:f8fb3d6c6a55872b0c6db077a28f5a8c7b8984b7c32029613d44cef95cfc0812\", size = 17205, upload-time = \"2026-02-20T04:02:07.299Z\" },\n ]\n \n+[[package]]\n+name = \"eval-type-backport\"\n+version = \"0.3.1\"\n+source = { registry = \"https://pypi.org/simple\" }\n+sdist = { url = \"https://files.pythonhosted.org/packages/fb/a3/cafafb4558fd638aadfe4121dc6cefb8d743368c085acb2f521df0f3d9d7/eval_type_backport-0.3.1.tar.gz\", hash = \"sha256:57e993f7b5b69d271e37482e62f74e76a0276c82490cf8e4f0dffeb6b332d5ed\", size = 9445, upload-time = \"2025-12-02T11:51:42.987Z\" }\n+wheels = [\n+    { url = \"https://files.pythonhosted.org/packages/cf/22/fdc2e30d43ff853720042fa15baa3e6122722be1a7950a98233ebb55cd71/eval_type_backport-0.3.1-py3-none-any.whl\", hash = \"sha256:279ab641905e9f11129f56a8a78f493518515b83402b860f6f06dd7c011fdfa8\", size = 6063, upload-time = \"2025-12-02T11:51:41.665Z\" },\n+]\n+\n [[package]]\n name = \"exa-py\"\n version = \"2.10.2\"","@@ -1643,7 +1652,7 @@ wheels = [\n \n [[package]]\n name = \"hermes-agent\"\n-version = \"0.7.0\"\n+version = \"0.8.0\"\n source = { editable = \".\" }\n dependencies = [\n     { name = \"anthropic\" },","@@ -1683,6 +1692,7 @@ all = [\n     { name = \"honcho-ai\" },\n     { name = \"lark-oapi\" },\n     { name = \"mcp\" },\n+    { name = \"mistralai\" },\n     { name = \"modal\" },\n     { name = \"numpy\" },\n     { name = \"ptyprocess\", marker = \"sys_platform != 'win32'\" },","@@ -1738,6 +1748,9 @@ messaging = [\n     { name = \"slack-bolt\" },\n     { name = \"slack-sdk\" },\n ]\n+mistral = [\n+    { name = \"mistralai\" },\n+]\n modal = [\n     { name = \"modal\" },\n ]","@@ -1803,6 +1816,7 @@ requires-dist = [\n     { name = \"hermes-agent\", extras = [\"honcho\"], marker = \"extra == 'all'\" },\n     { name = \"hermes-agent\", extras = [\"mcp\"], marker = \"extra == 'all'\" },\n     { name = \"hermes-agent\", extras = [\"messaging\"], marker = \"extra == 'all'\" },\n+    { name = \"hermes-agent\", extras = [\"mistral\"], marker = \"extra == 'all'\" },\n     { name = \"hermes-agent\", extras = [\"modal\"], marker = \"extra == 'all'\" },\n     { name = \"hermes-agent\", extras = [\"pty\"], marker = \"extra == 'all'\" },\n     { name = \"hermes-agent\", extras = [\"slack\"], marker = \"extra == 'all'\" },","@@ -1817,6 +1831,7 @@ requires-dist = [\n     { name = \"matrix-nio\", extras = [\"e2e\"], marker = \"extra == 'matrix'\", specifier = \">=0.24.0,<1\" },\n     { name = \"mcp\", marker = \"extra == 'dev'\", specifier = \">=1.2.0,<2\" },\n     { name = \"mcp\", marker = \"extra == 'mcp'\", specifier = \">=1.2.0,<2\" },\n+    { name = \"mistralai\", marker = \"extra == 'mistral'\", specifier = \">=2.3.0,<3\" },\n     { name = \"modal\", marker = \"extra == 'modal'\", specifier = \">=1.0.0,<2\" },\n     { name = \"numpy\", marker = \"extra == 'voice'\", specifier = \">=1.24.0,<3\" },\n     { name = \"openai\", specifier = \">=2.21.0,<3\" },","@@ -1846,7 +1861,7 @@ requires-dist = [\n     { name = \"wandb\", marker = \"extra == 'rl'\", specifier = \">=0.15.0,<1\" },\n     { name = \"yc-bench\", marker = \"python_full_version >= '3.12' and extra == 'yc-bench'\", git = \"https://github.com/collinear-ai/yc-bench.git\" },\n ]\n-provides-extras = [\"modal\", \"daytona\", \"dev\", \"messaging\", \"cron\", \"slack\", \"matrix\", \"cli\", \"tts-premium\", \"voice\", \"pty\", \"honcho\", \"mcp\", \"homeassistant\", \"sms\", \"acp\", \"dingtalk\", \"feishu\", \"rl\", \"yc-bench\", \"all\"]\n+provides-extras = [\"modal\", \"daytona\", \"dev\", \"messaging\", \"cron\", \"slack\", \"matrix\", \"cli\", \"tts-premium\", \"voice\", \"pty\", \"honcho\", \"mcp\", \"homeassistant\", \"sms\", \"acp\", \"mistral\", \"dingtalk\", \"feishu\", \"rl\", \"yc-bench\", \"all\"]\n \n [[package]]\n name = \"hf-transfer\"","@@ -2191,6 +2206,15 @@ wheels = [\n     { url = \"https://files.pythonhosted.org/packages/f8/62/d9ba6323b9202dd2fe166beab8a86d29465c41a0288cbe229fac60c1ab8d/jsonlines-4.0.0-py3-none-any.whl\", hash = \"sha256:185b334ff2ca5a91362993f42e83588a360cf95ce4b71a73548502bda52a7c55\", size = 8701, upload-time = \"2023-09-01T12:34:42.563Z\" },\n ]\n \n+[[package]]\n+name = \"jsonpath-python\"\n+version = \"1.1.5\"\n+source = { registry = \"https://pypi.org/simple\" }\n+sdist = { url = \"https://files.pythonhosted.org/packages/2d/db/2f4ecc24da35c6142b39c353d5b7c16eef955cc94b35a48d3fa47996d7c3/jsonpath_python-1.1.5.tar.gz\", hash = \"sha256:ceea2efd9e56add09330a2c9631ea3d55297b9619348c1055e5bfb9cb0b8c538\", size = 87352, upload-time = \"2026-03-17T06:16:40.597Z\" }\n+wheels = [\n+    { url = \"https://files.pythonhosted.org/packages/28/50/1a313fb700526b134c71eb8a225d8b83be0385dbb0204337b4379c698cef/jsonpath_python-1.1.5-py3-none-any.whl\", hash = \"sha256:a60315404d70a65e76c9a782c84e50600480221d94a58af47b7b4d437351cb4b\", size = 14090, upload-time = \"2026-03-17T06:16:39.152Z\" },\n+]\n+\n [[package]]\n name = \"jsonschema\"\n version = \"4.26.0\"","@@ -2616,6 +2640,25 @@ wheels = [\n     { url = \"https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl\", hash = \"sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8\", size = 9979, upload-time = \"2022-08-14T12:40:09.779Z\" },\n ]\n \n+[[package]]\n+name = \"mistralai\"\n+version = \"2.3.0\"\n+source = { registry = \"https://pypi.org/simple\" }\n+dependencies = [\n+    { name = \"eval-type-backport\" },\n+    { name = \"httpx\" },\n+    { name = \"jsonpath-python\" },\n+    { name = \"opentelemetry-api\" },\n+    { name = \"opentelemetry-semantic-conventions\" },\n+    { name = \"pydantic\" },\n+    { name = \"python-dateutil\" },\n+    { name = \"typing-inspection\" },\n+]\n+sdist = { url = \"https://files.pythonhosted.org/packages/4d/05/40c38c8893f0ec858756b30f4a939378fc62cf33565af538a843497f3f24/mistralai-2.3.0.tar.gz\", hash = \"sha256:eb371a9b3b62552f3d4a274ecf5b2c48b90fd3439ecd1425e7f5163cdd87e29a\", size = 387145, upload-time = \"2026-04-03T15:06:48.927Z\" }\n+wheels = [\n+    { url = \"https://files.pythonhosted.org/packages/bd/57/d06cbfd96ec6dc45d5c1fe9456f7fcfcb9549c9fa91e213561d1d88729e7/mistralai-2.3.0-py3-none-any.whl\", hash = \"sha256:22111747c215f1632141660151924f06579f87cd8db2649e0b1f87721d076851\", size = 925544, upload-time = \"2026-04-03T15:06:47.593Z\" },\n+]\n+\n [[package]]\n name = \"modal\"\n version = \"1.3.4\"","@@ -3073,32 +3116,32 @@ wheels = [\n \n [[package]]\n name = \"opentelemetry-api\"\n-version = \"1.40.0\"\n+version = \"1.39.1\"\n source = { registry = \"https://pypi.org/simple\" }\n dependencies = [\n     { name = \"importlib-metadata\" },\n     { name = \"typing-extensions\" },\n ]\n-sdist = { url = \"https://files.pythonhosted.org/packages/2c/1d/4049a9e8698361cc1a1aa03a6c59e4fa4c71e0c0f94a30f988a6876a2ae6/opentelemetry_api-1.40.0.tar.gz\", hash = \"sha256:159be641c0b04d11e9ecd576906462773eb97ae1b657730f0ecf64d32071569f\", size = 70851, upload-time = \"2026-03-04T14:17:21.555Z\" }\n+sdist = { url = \"https://files.pythonhosted.org/packages/97/b9/3161be15bb8e3ad01be8be5a968a9237c3027c5be504362ff800fca3e442/opentelemetry_api-1.39.1.tar.gz\", hash = \"sha256:fbde8c80e1b937a2c61f20347e91c0c18a1940cecf012d62e65a7caf08967c9c\", size = 65767, upload-time = \"2025-12-11T13:32:39.182Z\" }\n wheels = [\n-    { url = \"https://files.pythonhosted.org/packages/5f/bf/93795954016c522008da367da292adceed71cca6ee1717e1d64c83089099/opentelemetry_api-1.40.0-py3-none-any.whl\", hash = \"sha256:82dd69331ae74b06f6a874704be0cfaa49a1650e1537d4a813b86ecef7d0ecf9\", size = 68676, upload-time = \"2026-03-04T14:17:01.24Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/cf/df/d3f1ddf4bb4cb50ed9b1139cc7b1c54c34a1e7ce8fd1b9a37c0d1551a6bd/opentelemetry_api-1.39.1-py3-none-any.whl\", hash = \"sha256:2edd8463432a7f8443edce90972169b195e7d6a05500cd29e6d13898187c9950\", size = 66356, upload-time = \"2025-12-11T13:32:17.304Z\" },\n ]\n \n [[package]]\n name = \"opentelemetry-exporter-otlp-proto-common\"\n-version = \"1.40.0\"\n+version = \"1.39.1\"\n source = { registry = \"https://pypi.org/simple\" }\n dependencies = [\n     { name = \"opentelemetry-proto\" },\n ]\n-sdist = { url = \"https://files.pythonhosted.org/packages/51/bc/1559d46557fe6eca0b46c88d4c2676285f1f3be2e8d06bb5d15fbffc814a/opentelemetry_exporter_otlp_proto_common-1.40.0.tar.gz\", hash = \"sha256:1cbee86a4064790b362a86601ee7934f368b81cd4cc2f2e163902a6e7818a0fa\", size = 20416, upload-time = \"2026-03-04T14:17:23.801Z\" }\n+sdist = { url = \"https://files.pythonhosted.org/packages/e9/9d/22d241b66f7bbde88a3bfa6847a351d2c46b84de23e71222c6aae25c7050/opentelemetry_exporter_otlp_proto_common-1.39.1.tar.gz\", hash = \"sha256:763370d4737a59741c89a67b50f9e39271639ee4afc999dadfe768541c027464\", size = 20409, upload-time = \"2025-12-11T13:32:40.885Z\" }\n wheels = [\n-    { url = \"https://files.pythonhosted.org/packages/8b/ca/8f122055c97a932311a3f640273f084e738008933503d0c2563cd5d591fc/opentelemetry_exporter_otlp_proto_common-1.40.0-py3-none-any.whl\", hash = \"sha256:7081ff453835a82417bf38dccf122c827c3cbc94f2079b03bba02a3165f25149\", size = 18369, upload-time = \"2026-03-04T14:17:04.796Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/8c/02/ffc3e143d89a27ac21fd557365b98bd0653b98de8a101151d5805b5d4c33/opentelemetry_exporter_otlp_proto_common-1.39.1-py3-none-any.whl\", hash = \"sha256:08f8a5862d64cc3435105686d0216c1365dc5701f86844a8cd56597d0c764fde\", size = 18366, upload-time = \"2025-12-11T13:32:20.2Z\" },\n ]\n \n [[package]]\n name = \"opentelemetry-exporter-otlp-proto-http\"\n-version = \"1.40.0\"\n+version = \"1.39.1\"\n source = { registry = \"https://pypi.org/simple\" }\n dependencies = [\n     { name = \"googleapis-common-protos\" },","@@ -3109,14 +3152,14 @@ dependencies = [\n     { name = \"requests\" },\n     { name = \"typing-extensions\" },\n ]\n-sdist = { url = \"https://files.pythonhosted.org/packages/2e/fa/73d50e2c15c56be4d000c98e24221d494674b0cc95524e2a8cb3856d95a4/opentelemetry_exporter_otlp_proto_http-1.40.0.tar.gz\", hash = \"sha256:db48f5e0f33217588bbc00274a31517ba830da576e59503507c839b38fa0869c\", size = 17772, upload-time = \"2026-03-04T14:17:25.324Z\" }\n+sdist = { url = \"https://files.pythonhosted.org/packages/80/04/2a08fa9c0214ae38880df01e8bfae12b067ec0793446578575e5080d6545/opentelemetry_exporter_otlp_proto_http-1.39.1.tar.gz\", hash = \"sha256:31bdab9745c709ce90a49a0624c2bd445d31a28ba34275951a6a362d16a0b9cb\", size = 17288, upload-time = \"2025-12-11T13:32:42.029Z\" }\n wheels = [\n-    { url = \"https://files.pythonhosted.org/packages/a0/3a/8865d6754e61c9fb170cdd530a124a53769ee5f740236064816eb0ca7301/opentelemetry_exporter_otlp_proto_http-1.40.0-py3-none-any.whl\", hash = \"sha256:a8d1dab28f504c5d96577d6509f80a8150e44e8f45f82cdbe0e34c99ab040069\", size = 19960, upload-time = \"2026-03-04T14:17:07.153Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/95/f1/b27d3e2e003cd9a3592c43d099d2ed8d0a947c15281bf8463a256db0b46c/opentelemetry_exporter_otlp_proto_http-1.39.1-py3-none-any.whl\", hash = \"sha256:d9f5207183dd752a412c4cd564ca8875ececba13be6e9c6c370ffb752fd59985\", size = 19641, upload-time = \"2025-12-11T13:32:22.248Z\" },\n ]\n \n [[package]]\n name = \"opentelemetry-instrumentation\"\n-version = \"0.61b0\"\n+version = \"0.60b1\"\n source = { registry = \"https://pypi.org/simple\" }\n dependencies = [\n     { name = \"opentelemetry-api\" },","@@ -3124,14 +3167,14 @@ dependencies = [\n     { name = \"packaging\" },\n     { name = \"wrapt\" },\n ]\n-sdist = { url = \"https://files.pythonhosted.org/packages/da/37/6bf8e66bfcee5d3c6515b79cb2ee9ad05fe573c20f7ceb288d0e7eeec28c/opentelemetry_instrumentation-0.61b0.tar.gz\", hash = \"sha256:cb21b48db738c9de196eba6b805b4ff9de3b7f187e4bbf9a466fa170514f1fc7\", size = 32606, upload-time = \"2026-03-04T14:20:16.825Z\" }\n+sdist = { url = \"https://files.pythonhosted.org/packages/41/0f/7e6b713ac117c1f5e4e3300748af699b9902a2e5e34c9cf443dde25a01fa/opentelemetry_instrumentation-0.60b1.tar.gz\", hash = \"sha256:57ddc7974c6eb35865af0426d1a17132b88b2ed8586897fee187fd5b8944bd6a\", size = 31706, upload-time = \"2025-12-11T13:36:42.515Z\" }\n wheels = [\n-    { url = \"https://files.pythonhosted.org/packages/d8/3e/f6f10f178b6316de67f0dfdbbb699a24fbe8917cf1743c1595fb9dcdd461/opentelemetry_instrumentation-0.61b0-py3-none-any.whl\", hash = \"sha256:92a93a280e69788e8f88391247cc530fd81f16f2b011979d4d6398f805cfbc63\", size = 33448, upload-time = \"2026-03-04T14:19:02.447Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/77/d2/6788e83c5c86a2690101681aeef27eeb2a6bf22df52d3f263a22cee20915/opentelemetry_instrumentation-0.60b1-py3-none-any.whl\", hash = \"sha256:04480db952b48fb1ed0073f822f0ee26012b7be7c3eac1a3793122737c78632d\", size = 33096, upload-time = \"2025-12-11T13:35:33.067Z\" },\n ]\n \n [[package]]\n name = \"opentelemetry-instrumentation-aiohttp-client\"\n-version = \"0.61b0\"\n+version = \"0.60b1\"\n source = { registry = \"https://pypi.org/simple\" }\n dependencies = [\n     { name = \"opentelemetry-api\" },","@@ -3140,57 +3183,57 @@ dependencies = [\n     { name = \"opentelemetry-util-http\" },\n     { name = \"wrapt\" },\n ]\n-sdist = { url = \"https://files.pythonhosted.org/packages/6f/6d/24fed4de661de107f2426b28bbd87b51eaab28a2339b62f269a36ae24505/opentelemetry_instrumentation_aiohttp_client-0.61b0.tar.gz\", hash = \"sha256:c53ab3b88efcb7ce98c1129cc0389f0a1f214eb3675269b6c157770adcf47877\", size = 19292, upload-time = \"2026-03-04T14:20:18.408Z\" }\n+sdist = { url = \"https://files.pythonhosted.org/packages/c0/79/95be90c555fd7efde79dcba36ea5c668815aa2d0a4250b63687e0f91c74a/opentelemetry_instrumentation_aiohttp_client-0.60b1.tar.gz\", hash = \"sha256:d0e7d5aa057791ca4d9090b0d3c9982f253c1a24b6bc78a734fc18d8dd97927b\", size = 15907, upload-time = \"2025-12-11T13:36:44.434Z\" }\n wheels = [\n-    { url = \"https://files.pythonhosted.org/packages/df/f3/1edc42716521a3f754ac32ffb908f102e0f131f8e43fcd9ab29cab286723/opentelemetry_instrumentation_aiohttp_client-0.61b0-py3-none-any.whl\", hash = \"sha256:09bc47514c162507b357366ce15578743fd6305078cf7d872db1c99c13fa6972\", size = 14534, upload-time = \"2026-03-04T14:19:05.165Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/ca/f4/1a1ec632c86269750ae833c8fbdd4c8d15316eb1c21e3544e34791c805ee/opentelemetry_instrumentation_aiohttp_client-0.60b1-py3-none-any.whl\", hash = \"sha256:34c5097256a30b16c5a2a88a409ed82b92972a494c43212c85632d204a78c2a1\", size = 12694, upload-time = \"2025-12-11T13:35:35.034Z\" },\n ]\n \n [[package]]\n name = \"opentelemetry-proto\"\n-version = \"1.40.0\"\n+version = \"1.39.1\"\n source = { registry = \"https://pypi.org/simple\" }\n dependencies = [\n     { name = \"protobuf\" },\n ]\n-sdist = { url = \"https://files.pythonhosted.org/packages/4c/77/dd38991db037fdfce45849491cb61de5ab000f49824a00230afb112a4392/opentelemetry_proto-1.40.0.tar.gz\", hash = \"sha256:03f639ca129ba513f5819810f5b1f42bcb371391405d99c168fe6937c62febcd\", size = 45667, upload-time = \"2026-03-04T14:17:31.194Z\" }\n+sdist = { url = \"https://files.pythonhosted.org/packages/49/1d/f25d76d8260c156c40c97c9ed4511ec0f9ce353f8108ca6e7561f82a06b2/opentelemetry_proto-1.39.1.tar.gz\", hash = \"sha256:6c8e05144fc0d3ed4d22c2289c6b126e03bcd0e6a7da0f16cedd2e1c2772e2c8\", size = 46152, upload-time = \"2025-12-11T13:32:48.681Z\" }\n wheels = [\n-    { url = \"https://files.pythonhosted.org/packages/b9/b2/189b2577dde745b15625b3214302605b1353436219d42b7912e77fa8dc24/opentelemetry_proto-1.40.0-py3-none-any.whl\", hash = \"sha256:266c4385d88923a23d63e353e9761af0f47a6ed0d486979777fe4de59dc9b25f\", size = 72073, upload-time = \"2026-03-04T14:17:16.673Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/51/95/b40c96a7b5203005a0b03d8ce8cd212ff23f1793d5ba289c87a097571b18/opentelemetry_proto-1.39.1-py3-none-any.whl\", hash = \"sha256:22cdc78efd3b3765d09e68bfbd010d4fc254c9818afd0b6b423387d9dee46007\", size = 72535, upload-time = \"2025-12-11T13:32:33.866Z\" },\n ]\n \n [[package]]\n name = \"opentelemetry-sdk\"\n-version = \"1.40.0\"\n+version = \"1.39.1\"\n source = { registry = \"https://pypi.org/simple\" }\n dependencies = [\n     { name = \"opentelemetry-api\" },\n     { name = \"opentelemetry-semantic-conventions\" },\n     { name = \"typing-extensions\" },\n ]\n-sdist = { url = \"https://files.pythonhosted.org/packages/58/fd/3c3125b20ba18ce2155ba9ea74acb0ae5d25f8cd39cfd37455601b7955cc/opentelemetry_sdk-1.40.0.tar.gz\", hash = \"sha256:18e9f5ec20d859d268c7cb3c5198c8d105d073714db3de50b593b8c1345a48f2\", size = 184252, upload-time = \"2026-03-04T14:17:31.87Z\" }\n+sdist = { url = \"https://files.pythonhosted.org/packages/eb/fb/c76080c9ba07e1e8235d24cdcc4d125ef7aa3edf23eb4e497c2e50889adc/opentelemetry_sdk-1.39.1.tar.gz\", hash = \"sha256:cf4d4563caf7bff906c9f7967e2be22d0d6b349b908be0d90fb21c8e9c995cc6\", size = 171460, upload-time = \"2025-12-11T13:32:49.369Z\" }\n wheels = [\n-    { url = \"https://files.pythonhosted.org/packages/2c/c5/6a852903d8bfac758c6dc6e9a68b015d3c33f2f1be5e9591e0f4b69c7e0a/opentelemetry_sdk-1.40.0-py3-none-any.whl\", hash = \"sha256:787d2154a71f4b3d81f20524a8ce061b7db667d24e46753f32a7bc48f1c1f3f1\", size = 141951, upload-time = \"2026-03-04T14:17:17.961Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/7c/98/e91cf858f203d86f4eccdf763dcf01cf03f1dae80c3750f7e635bfa206b6/opentelemetry_sdk-1.39.1-py3-none-any.whl\", hash = \"sha256:4d5482c478513ecb0a5d938dcc61394e647066e0cc2676bee9f3af3f3f45f01c\", size = 132565, upload-time = \"2025-12-11T13:32:35.069Z\" },\n ]\n \n [[package]]\n name = \"opentelemetry-semantic-conventions\"\n-version = \"0.61b0\"\n+version = \"0.60b1\"\n source = { registry = \"https://pypi.org/simple\" }\n dependencies = [\n     { name = \"opentelemetry-api\" },\n     { name = \"typing-extensions\" },\n ]\n-sdist = { url = \"https://files.pythonhosted.org/packages/6d/c0/4ae7973f3c2cfd2b6e321f1675626f0dab0a97027cc7a297474c9c8f3d04/opentelemetry_semantic_conventions-0.61b0.tar.gz\", hash = \"sha256:072f65473c5d7c6dc0355b27d6c9d1a679d63b6d4b4b16a9773062cb7e31192a\", size = 145755, upload-time = \"2026-03-04T14:17:32.664Z\" }\n+sdist = { url = \"https://files.pythonhosted.org/packages/91/df/553f93ed38bf22f4b999d9be9c185adb558982214f33eae539d3b5cd0858/opentelemetry_semantic_conventions-0.60b1.tar.gz\", hash = \"sha256:87c228b5a0669b748c76d76df6c364c369c28f1c465e50f661e39737e84bc953\", size = 137935, upload-time = \"2025-12-11T13:32:50.487Z\" }\n wheels = [\n-    { url = \"https://files.pythonhosted.org/packages/b2/37/cc6a55e448deaa9b27377d087da8615a3416d8ad523d5960b78dbeadd02a/opentelemetry_semantic_conventions-0.61b0-py3-none-any.whl\", hash = \"sha256:fa530a96be229795f8cef353739b618148b0fe2b4b3f005e60e262926c4d38e2\", size = 231621, upload-time = \"2026-03-04T14:17:19.33Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/7a/5e/5958555e09635d09b75de3c4f8b9cae7335ca545d77392ffe7331534c402/opentelemetry_semantic_conventions-0.60b1-py3-none-any.whl\", hash = \"sha256:9fa8c8b0c110da289809292b0591220d3a7b53c1526a23021e977d68597893fb\", size = 219982, upload-time = \"2025-12-11T13:32:36.955Z\" },\n ]\n \n [[package]]\n name = \"opentelemetry-util-http\"\n-version = \"0.61b0\"\n+version = \"0.60b1\"\n source = { registry = \"https://pypi.org/simple\" }\n-sdist = { url = \"https://files.pythonhosted.org/packages/57/3c/f0196223efc5c4ca19f8fad3d5462b171ac6333013335ce540c01af419e9/opentelemetry_util_http-0.61b0.tar.gz\", hash = \"sha256:1039cb891334ad2731affdf034d8fb8b48c239af9b6dd295e5fabd07f1c95572\", size = 11361, upload-time = \"2026-03-04T14:20:57.01Z\" }\n+sdist = { url = \"https://files.pythonhosted.org/packages/50/fc/c47bb04a1d8a941a4061307e1eddfa331ed4d0ab13d8a9781e6db256940a/opentelemetry_util_http-0.60b1.tar.gz\", hash = \"sha256:0d97152ca8c8a41ced7172d29d3622a219317f74ae6bb3027cfbdcf22c3cc0d6\", size = 11053, upload-time = \"2025-12-11T13:37:25.115Z\" }\n wheels = [\n-    { url = \"https://files.pythonhosted.org/packages/0d/e5/c08aaaf2f64288d2b6ef65741d2de5454e64af3e050f34285fb1907492fe/opentelemetry_util_http-0.61b0-py3-none-any.whl\", hash = \"sha256:8e715e848233e9527ea47e275659ea60a57a75edf5206a3b937e236a6da5fc33\", size = 9281, upload-time = \"2026-03-04T14:20:08.364Z\" },\n+    { url = \"https://files.pythonhosted.org/packages/16/5c/d3f1733665f7cd582ef0842fb1d2ed0bc1fba10875160593342d22bba375/opentelemetry_util_http-0.60b1-py3-none-any.whl\", hash = \"sha256:66381ba28550c91bee14dcba8979ace443444af1ed609226634596b4b0faf199\", size = 8947, upload-time = \"2025-12-11T13:36:37.151Z\" },\n ]\n \n [[package]]"]},{"file":"website/docs/user-guide/features/tts.md","hunks":["@@ -102,11 +102,13 @@ Local transcription works out of the box when `faster-whisper` is installed. If\n ```yaml\n # In ~/.hermes/config.yaml\n stt:\n-  provider: \"local\"           # \"local\" | \"groq\" | \"openai\"\n+  provider: \"local\"           # \"local\" | \"groq\" | \"openai\" | \"mistral\"\n   local:\n     model: \"base\"             # tiny, base, small, medium, large-v3\n   openai:\n     model: \"whisper-1\"        # whisper-1, gpt-4o-mini-transcribe, gpt-4o-transcribe\n+  mistral:\n+    model: \"voxtral-mini-latest\"  # voxtral-mini-latest, voxtral-mini-2602\n ```\n \n ### Provider Details","@@ -125,6 +127,8 @@ stt:\n \n **OpenAI API** — Accepts `VOICE_TOOLS_OPENAI_KEY` first and falls back to `OPENAI_API_KEY`. Supports `whisper-1`, `gpt-4o-mini-transcribe`, and `gpt-4o-transcribe`.\n \n+**Mistral API (Voxtral Transcribe)** — Requires `MISTRAL_API_KEY`. Uses Mistral's [Voxtral Transcribe](https://docs.mistral.ai/capabilities/audio/speech_to_text/) models. Supports 13 languages, speaker diarization, and word-level timestamps. Install with `pip install hermes-agent[mistral]`.\n+\n **Custom local CLI fallback** — Set `HERMES_LOCAL_STT_COMMAND` if you want Hermes to call a local transcription command directly. The command template supports `{input_path}`, `{output_dir}`, `{language}`, and `{model}` placeholders.\n \n ### Fallback Behavior","@@ -133,4 +137,5 @@ If your configured provider isn't available, Hermes automatically falls back:\n - **Local faster-whisper unavailable** → Tries a local `whisper` CLI or `HERMES_LOCAL_STT_COMMAND` before cloud providers\n - **Groq key not set** → Falls back to local transcription, then OpenAI\n - **OpenAI key not set** → Falls back to local transcription, then Groq\n+- **Mistral key/SDK not set** → Skipped in auto-detect; falls through to next available provider\n - **Nothing available** → Voice messages pass through with an accurate note to the user\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}