
    Fj?                     0   U d Z ddlZddlZddlZddlZddlZddlZddlmZ ddl	m
Z
mZmZ ddlmZ ddlmZ ddlmZ ddlmZmZmZ  ej        e          Zdqd	ZddlmZ d
edefdZ  e d          Z! e d          Z" e d          Z#dZ$dZ%dZ& ej'        dd          Z( ej'        dd          Z) ej'        dd          Z*dZ+dZ,dZ- ej'        dd          Z. ej'        dd          Z/ ej'        d d!          Z0h d"Z1h d#Z2d$Z3h d%Z4h d&Z5da6e
e7         e8d'<   da9e
e         e8d(<   de:fd)Z;dqd*e
e:         defd+Z<defd,Z=d-ede
e         fd.Z>de
e         fd/Z?de
e         fd0Z@de
e         fd1ZAdefd2ZBd3e
e         defd4ZCd3e
e         defd5ZDdefd6ZE eFh d7          ZGd8ZHdZId9ZJ eFh d:          ZKd*eeef         d;edeeef         fd<ZLd*eeef         d;edeeef         fd=ZMd>eeef         defd?ZNd@ed*eeef         de
eeef                  fdAZOd*eeef         fdBZPdqd*e
eeef                  defdCZQd>eeef         deRfdDZSd>eeef         defdEZTdFedGeUde
e         fdHZVdIedJe
e         defdKZWdFedLeeef         defdMZXdNejY        ddfdOZZdPedQeRdej[        fdRZ\dSedTedUedefdVZ]	 dqdWedXed>eeef         d*eeef         dYe
e         deeef         fdZZ^d*e:defd[Z_	 dqddd\dWed@ed*e
eeef                  d]e
e         d^e
e         de
eeef                  fd_Z`dWede
eeef                  fd`ZadaZbdbecdefdcZdd3efddZedWed3edeeef         fdeZfdWedfedege
e         e
e         f         fdgZhdWed3edeeef         fdhZidWed3edeeef         fdiZjdWed3edeeef         fdjZkdWed3edeeef         fdkZldWed3edeeef         fdlZmdqdWed]e
e         deeef         fdmZndegeef         fdnZodoedefdpZpdS )ru  
Transcription Tools Module

Provides speech-to-text transcription with six providers:

  - **local** (default, free) — faster-whisper running locally, no API key needed.
    Auto-downloads the model (~150 MB for ``base``) on first use.
  - **groq** (free tier) — Groq Whisper API, requires ``GROQ_API_KEY``.
  - **openai** (paid) — OpenAI Whisper API, requires ``VOICE_TOOLS_OPENAI_KEY``.
  - **mistral** — Mistral Voxtral Transcribe API, requires ``MISTRAL_API_KEY``.
  - **xai** — xAI Grok STT API, requires ``XAI_API_KEY``. High accuracy,
    Inverse Text Normalization, diarization, 21 languages.

Used by the messaging gateway to automatically transcribe voice messages
sent by users on Telegram, Discord, WhatsApp, Slack, and Signal.

Supported input formats: mp3, mp4, mpeg, mpga, m4a, wav, webm, ogg, aac

Usage::

    from tools.transcription_tools import transcribe_audio

    result = transcribe_audio("/path/to/audio.ogg")
    if result["success"]:
        print(result["transcript"])
    N)Path)OptionalDictAny)urljoin)is_truthy_value)resolve_managed_tool_gateway)managed_nous_tools_enabled%nous_tool_gateway_unavailable_messageresolve_openai_audio_api_keyc                 ~    	 ddl m} n%# t          $ r t          j        | |          cY S w xY w ||           }||n|S )a  Read env values through the live config module.

    Tests may monkeypatch and later restore ``hermes_cli.config.get_env_value``
    before this module is imported. Resolve the helper at call time so STT does
    not keep a stale imported function for the rest of the test process.
    r   )get_env_value)hermes_cli.configr   ImportErrorosgetenv)namedefault_get_env_valuevalues       8/usr/local/lib/hermes-agent/tools/transcription_tools.pyr   r   1   sl    (EEEEEEE ( ( (yw'''''(N4  Em77.s   	 ++module_namereturnc                     	 t          j        |           d uS # t          t          f$ r& | t	                      v p| t
          j        j        v cY S w xY wN)_ilu	find_specr   
ValueErrorglobalsr   sysmodules)r   s    r   _safe_find_specr"   F   sd    I~k**$66$ I I Igii'H;"&.+HHHHIs    4AAfaster_whisperopenai	mistralailocalbaseenSTT_OPENAI_MODEL	whisper-1STT_GROQ_MODELwhisper-large-v3-turboSTT_MISTRAL_MODELzvoxtral-mini-latestHERMES_LOCAL_STT_COMMANDHERMES_LOCAL_STT_LANGUAGE)z/opt/homebrew/binz/usr/local/binGROQ_BASE_URLzhttps://api.groq.com/openai/v1STT_OPENAI_BASE_URLzhttps://api.openai.com/v1XAI_STT_BASE_URLzhttps://api.x.ai/v1>
   .aac.m4a.mp3.mp4.ogg.flac.mpeg.mpga.webm.wav>   .aif.aiffr<   i  >   gpt-4o-transcribegpt-4o-mini-transcriber*   >   whisper-large-v3distil-whisper-large-v3-enr,   _local_model_local_model_namec                  p    	 ddl m}   |                                 di           S # t          $ r i cY S w xY w)zDLoad the ``stt`` section from user config, falling back to defaults.r   load_configstt)r   rG   get	ExceptionrF   s    r   _load_stt_configrK   u   sY    111111{}}  +++   			s   #& 55
stt_configc                 p    | t                      } |                     dd          }t          |d          S )z(Return whether STT is enabled in config.NenabledT)r   )rK   rI   r   )rL   rN   s     r   is_stt_enabledrO   ~   s9    %''
nnY--G7D1111    c                  F    	 t                       dS # t          $ r Y dS w xY w)zbReturn True when OpenAI audio can use config credentials, env credentials, or the managed gateway.TF)#_resolve_openai_audio_client_configr    rP   r   _has_openai_audio_backendrT      s:    +---t   uus    
  binary_namec                     t           D ]X}t          |          | z  }|                                r0t          j        |t          j                  rt          |          c S Yt          j        |           S )zMFind a local binary, checking common Homebrew/local prefixes as well as PATH.)	COMMON_LOCAL_BIN_DIRSr   existsr   accessX_OKstrshutilwhich)rU   	directory	candidates      r   _find_binaryr`      sm    * " "	OOk1	 	"")Irw"?"? 	"y>>!!!<$$$rP   c                       t          d          S )Nffmpegr`   rS   rP   r   _find_ffmpeg_binaryrd      s    !!!rP   c                       t          d          S )Nwhisperrc   rS   rP   r   _find_whisper_binaryrg      s    	"""rP   c                      t          j        t          d                                          } | r| S t	                      }|rt          j        |          }| dS d S )N za {input_path} --model {model} --output_format txt --output_dir {output_dir} --language {language})r   r   LOCAL_STT_COMMAND_ENVstriprg   shlexquote)
configuredwhisper_binaryquoted_binarys      r   _get_local_command_templaterq      sn    0"55;;==J )++N 
N33 > > >	
 4rP   c                  "    t                      d uS r   )rq   rS   rP   r   _has_local_commandrs      s    &((44rP   
model_namec                     | r| t           v s	| t          v r<| r3| t           v s	| t          v r!t                              d| t                     t          S | S )a  Return a valid faster-whisper model size, mapping cloud-only names to the default.

    Cloud providers like OpenAI use names such as ``whisper-1`` which are not
    valid for faster-whisper (which expects ``tiny``, ``base``, ``small``,
    ``medium``, or ``large-v*``).  When such a name is detected we fall back to
    the default local model and emit a warning so the user knows what happened.
    zSTT model '%s' is a cloud-only name and cannot be used with the local provider. Falling back to '%s'. Set stt.local.model to a valid faster-whisper size (tiny, base, small, medium, large-v3).)OPENAI_MODELSGROQ_MODELSloggerwarningDEFAULT_LOCAL_MODELrt   s    r   _normalize_local_modelr|      sn      	#}44
k8Q8Q 	:66*:S:SNNM #   #"rP   c                      t          |           S r   )r|   r{   s    r   _normalize_local_command_modelr~      s    !*---rP   c                      	 ddl m}   | d           ddlm} |                    d          rdS n2# t
          $ r%}t                              d|           Y d}~nd}~ww xY wdS )	ao  Attempt to lazy-install faster-whisper and return True on success.

    The module-level ``_HAS_FASTER_WHISPER`` flag is set at import time and
    cached. If the package wasn't installed at startup, calling ``ensure()``
    installs it. This function re-checks dynamically after installation so
    the provider can use it immediately without a process restart.
    r   )ensurezstt.faster_whisperNr#   Tz)Lazy install of faster-whisper failed: %sF)tools.lazy_depsr   importlib.utilutilr   rJ   rx   debug)r   _iuexcs      r   _try_lazy_install_sttr      s    G******#$$$$$$$$$==)** 	4	 G G G@#FFFFFFFFG5s   ,1 
A AA >   xaigroqr&   r$   mistrallocal_command,  txt>   srtr   vttjsonr   c                     t          | t                    si S |                     |          }t          |t                    r|ni S )z=Return an stt sub-section if it's a dict, else an empty dict.)
isinstancedictrI   )rL   r   sections      r   _get_stt_sectionr   
  sC    j$'' 	nnT""G $//777R7rP   c                 
   t          | d          }t          |t                    r|                    |          nd}t          |t                    r|S |                                t
          vrt          | |          }|r|S i S )u  Return the config dict for a user-declared STT command provider.

    Looks up ``stt.providers.<name>`` first (the canonical location), and
    falls back to ``stt.<name>`` so users who followed the built-in layout
    still work. Returns an empty dict when the provider is not declared.

    Built-in names are NOT special-cased here — the caller short-circuits
    them before this is consulted, AND ``_is_command_stt_provider_config``
    requires an explicit ``command:`` value, so a built-in section like
    ``stt.openai`` (which has ``model``/``language`` but no ``command``)
    can't accidentally be treated as a command provider.
    	providersN)r   r   r   rI   lowerBUILTIN_STT_PROVIDERS)rL   r   r   r   legacys        r   _get_named_stt_provider_configr     s      ![99I%/	4%@%@JimmD!!!dG'4    zz||000!*d33 	MIrP   configc                 j   t          | t                    sdS t          |                     d          pd                                                                          }|r|dk    rdS |                     d          }t          |t                    o t          |                                          S )z?Return True when *config* declares a command-type STT provider.Ftyperi   command)r   r   r[   rI   rk   r   bool)r   ptyper   s      r   _is_command_stt_provider_configr   0  s    fd## u

6""(b))//117799E )##ujj##Ggs##=W]]__(=(==rP   providerc                     | sdS |                                                                  }|t          v s|dk    rdS t          ||          }t	          |          r|S dS )zReturn the provider config if *provider* resolves to a command type.

    Built-in provider names are rejected (they have native handlers).
    Returns None when the name is a built-in, ``"none"``, unknown, or not
    a command type.
    Nnone)r   rk   r   r   r   )r   rL   keyr   s       r   $_resolve_command_stt_provider_configr   ;  sn      t
..


 
 
"
"C
###sf}}t+J<<F&v.. 4rP   c              #     K   t          | t                    sdS t          | d          }|pi                                 D ]J\  }}t          |t                    r0|                                t          vrt          |          r||fV  KdS )zHYield (name, config) pairs for every declared command-type STT provider.Nr   )r   r   r   itemsr[   r   r   r   )rL   r   r   cfgs       r   _iter_command_stt_providersr   P  s      j$''  [99Io2,,..    	cdC   	 TZZ\\9N%N%N.s33  Ci   rP   c                 T    | t                      } t          |           D ]\  }} dS dS )z=Return True when any command-type STT provider is configured.NTF)rK   r   )rL   _name_cfgs      r   _has_any_command_stt_providerr   [  s:    %''
2:>>  ttt5rP   c                    |                      d|                      dt                              }	 t          |          }n+# t          t          f$ r t          t                    cY S w xY w|dk    rt          t                    S |S )z5Return timeout in seconds, falling back when invalid.timeouttimeout_secondsr   )rI   #DEFAULT_COMMAND_STT_TIMEOUT_SECONDSfloat	TypeErrorr   )r   rawr   s      r   _get_command_stt_timeoutr   d  s    
**Y

+<>a b b
c
cC:c

z" : : :899999:zz8999Ls   A %A)(A)c                    |                      d          p|                      d          pt          }t          |                                                                                              d          }|t          v r|nt          S )z6Return the validated output format (txt/json/srt/vtt).formatoutput_format.)rI   !DEFAULT_COMMAND_STT_OUTPUT_FORMATr[   r   rk   lstripCOMMAND_STT_OUTPUT_FORMATS)r   r   fmts      r   _get_command_stt_output_formatr   p  s{     	

8 	-::o&&	-, 
 c((..


 
 
"
"
)
)#
.
.C333339ZZrP   command_templatepositionc                     d}d}d}||k     r\| |         }|dk    r	|dk    rd}n:|dk    r|rd}n/|dk    rd}n&|dk    rd}n|dk    rd}n|dk    rd}n|dk    r|dz  }|dz  }||k     \|S )	u  Return the shell quote character active right before *position*.

    Mirrors ``tools.tts_tool._shell_quote_context`` — kept local to avoid
    cross-module import of a private helper. Returns ``"'"`` / ``'"'`` when
    inside a quoted region, ``None`` for bare context.
    NFr   '"\T   rS   )r   r   rm   escapedichars         r   _shell_quote_context_sttr   {  s      EG	A
h,,"C<<s{{c\\ S[[EES[[EET\\FA	Q% h,,& LrP   r   quote_contextc                 \   |dk    r|                      dd          S |dk    rR|                      dd                               dd                               dd                               d	d
          S t          j        dk    rt          j        | g          S t          j        |           S )zQuote a placeholder value for its position in a shell command template.

    Mirrors ``tools.tts_tool._quote_command_tts_placeholder``.
    r   z'\''r   r   z\\z\"$z\$`z\`nt)replacer   r   
subprocesslist2cmdlinerl   rm   )r   r   s     r   _quote_command_stt_placeholderr     s    
 }}S'***WT6""WS%  WS%  WS%  	
 
w$&w///;urP   placeholdersc                 r   	 ddl d                    fdD                       }                    d| d| d          }g 	dd	d
t          f 	fd}|                    |           }|                    dd                              dd          }	D ]\  }}|                    ||          }|S )a  Replace supported placeholders while preserving ``{{`` / ``}}``.

    Mirrors ``tools.tts_tool._render_command_tts_template``. Placeholders
    are shell-quote-aware: ``{voice}`` inside single quotes gets
    single-quote-safe escaping, inside double quotes gets ``$``/`` ` ``/`` " ``
    escaping, outside quotes gets ``shlex.quote``. Doubled braces ``{{`` and
    ``}}`` are preserved as literal ``{`` / ``}`` for users who want to
    embed JSON snippets in their command.
    r   N|c              3   B   K   | ]}                     |          V  d S r   )escape).0r   res     r   	<genexpr>z/_render_command_stt_template.<locals>.<genexpr>  s-      >>RYYt__>>>>>>rP   z(?<!\$)(?:\{\{(?P<double>z)\}\}|\{(?P<single>z)\})matchzre.Match[str]r   c                    |                      d          p|                      d          }dt                     d}                    |t          |         t	          |                                                     f           |S )Ndoublesingle__HERMES_STT_PLACEHOLDER___)grouplenappendr   r   start)r   r   tokenr   r   replacementss      r   replace_matchz3_render_command_stt_template.<locals>.replace_match  s    {{8$$=H(=(=AC,=,=AAA*T"()95;;==II 
 	 	 	 rP   z{{{z}}})r   joincompiler[   subr   )
r   r   namespatternr   renderedr   r   r   r   s
   ``      @@r   _render_command_stt_templater     s    IIIHH>>>>>>>>>EjjPuPPEPPP G +-L
_ 
 
 
 
 
 
 
 
 
 {{=*:;;Hc**224==H$ 2 2u##E511OrP   procc           	         |                                  dS t          j        dk    rk	 t          j        ddddt          | j                  gt          j        t          j        d           n$# t          $ r | 	                                 Y nw xY wdS 	 d	dl
}nf# t          $ rY |                                  	 |                     d
           n)# t          j        $ r | 	                                 Y nw xY wY dS w xY w	 |                    | j                  }|                    d          D ]'}	 |                                 # |j        $ r Y $w xY w|                                 n0# |j        $ r Y dS t          $ r |                                  Y nw xY w	 |                     d
           dS # t          j        $ r Y nw xY w	 |                    | j                  }|                    d          D ]'}	 |	                                 # |j        $ r Y $w xY w|	                                 dS # |j        $ r Y dS t          $ r | 	                                 Y dS w xY w)zBest-effort termination of a shell process and all of its children.

    Mirrors ``tools.tts_tool._terminate_command_tts_process_tree``.
    Nr   taskkillz/Fz/Tz/PID   )stdoutstderrr   r      r   T)	recursive)pollr   r   r   runr[   pidDEVNULLrJ   killpsutilr   	terminatewaitTimeoutExpiredProcesschildrenNoSuchProcess)r   r  parentchilds       r   #_terminate_command_stt_process_treer
    s   
 yy{{	w$	NT4TX?!)!)	      	 	 	IIKKKKK		   	IIaI    ( 	 	 	IIKKKKK	))__t_44 	 	E!!!!'         		!	$   ))__t_44 	 	E

'         		s   AA, ,BBB C;7CC;#C41C;3C44C;:C;?2E, 2EE, 
EE, EE, ,
F9FFF5 5GG2H9 >HH9 
H H9 H  H9 9
I'I'&I'r   r   c                    dt           j        t           j        dd}t          j        dk    rt	          t           dd          |d<   nd|d<   t          j        | fi |}	 |                    |          \  }}n# t           j        $ rz}t          |           	 |                    d	          \  }}n2# t          $ r% t	          |d
d          }t	          |dd          }Y nw xY wt          j        | |||          |d}~ww xY w|j
        rt          j        |j
        | ||          t          j        | |j
        ||          S )z~Run a command-provider shell command with process-tree timeout cleanup.

    Mirrors ``tools.tts_tool._run_command_tts``.
    T)shellr   r   textr   CREATE_NEW_PROCESS_GROUPr   creationflagsstart_new_sessionr   r   outputNr   )r  r   )r   PIPEr   r   getattrPopencommunicater  r
  rJ   
returncodeCalledProcessErrorCompletedProcess)r   r   popen_kwargsr   r   r   r   s          r   _run_command_sttr    s    //	$ $L 
w$(/
<VXY(Z(Z_%%,0()G44|44D))')::$   +D111	2!--a-88NFFF 	2 	2 	2S(D11FS(D11FFF	2 '	
 
 

 	  
+O	
 
 
 	
 &wPPPs<   A8 8DC<B10C<1,C C<C  C<<Doutput_pathr   r   c                    |                                  ry	 |                     d                                          }nK# t          $ r> |                                                     dd                                          }Y nw xY w|r|S |r(|                                r|                                S t          d|  d          )u  Return the transcript text from a command-provider invocation.

    Resolution:
      1. If ``output_path`` exists and is non-empty → read it (raw text).
      2. Else if ``stdout`` is non-empty → use stdout (lets users write
         curl-style one-liners that emit transcript to stdout instead of
         writing a file).
      3. Else → raise RuntimeError (no usable output produced).

    For JSON format, we still return the raw bytes — extracting a
    ``text`` field is out of scope; users either configure ``format: txt``
    or post-process JSON downstream. (Same trade-off as TTS: the runner
    doesn't try to be clever about output shape.)
    utf-8encodingr   )errorsz-Command STT provider wrote no output file at z and produced no stdout)rX   	read_textrk   UnicodeDecodeError
read_bytesdecodeRuntimeError)r  r   r   contents       r   _read_command_stt_outputr'  B  s      	Y!++W+==CCEEGG! 	Y 	Y 	Y!,,..55gi5PPVVXXGGG	Y 	N &,,.. ||~~
	" 	" 	" 	"  s   (? ABB	file_pathprovider_namemodel_overridec                    t          |                    d          pd                                          }|sdd|d| ddS t          |                                           }|                                s
dd|d|  dS t          |          }t          |          }|                    d          p|                    d          pt          }	|p|                    d	          pd}
	 t          j
        d
| d          5 }t          |          d| z  }t          |                                          t          |          t          |j                  |t          |	          t          |
          d}t          ||          }t                              d|j        |           	 t#          ||          }n# t$          j        $ r dd|d| d|dddcY cddd           S t$          j        $ r}g }|j        r/|                    d|j                                                    |j        r/|                    d|j                                                    d                    |          pd}dd|d| d|j         d| dcY d}~cddd           S d}~ww xY w	 t5          ||j        pd|          }n7# t6          $ r*}dd|t          |          dcY d}~cddd           S d}~ww xY w	 ddd           n# 1 swxY w Y   n$# t8          $ r}dd|d| d| dcY d}~S d}~ww xY wt                              d|j        |t;          |                     d||dS )a,  Transcribe via a user-declared ``stt.providers.<name>: type: command``.

    Placeholder grammar:

    | Placeholder       | Substituted with                                          |
    |-------------------|-----------------------------------------------------------|
    | ``{input_path}``  | absolute path to the audio file (original location)       |
    | ``{output_path}`` | absolute path the provider should write its transcript to |
    | ``{output_dir}``  | parent dir of ``{output_path}``                           |
    | ``{format}``      | configured output format (``txt`` / ``json`` / ``srt`` / ``vtt``) |
    | ``{language}``    | configured language code (default ``en``)                 |
    | ``{model}``       | configured model id (empty when not set)                  |

    All placeholders are shell-quote-aware (see ``_render_command_stt_template``).
    Doubled braces ``{{`` and ``}}`` are preserved as literal braces.

    Returns the standard transcribe-response envelope (``success``,
    ``transcript``, ``provider``, ``error``).
    r   ri   Fzstt.providers.z.command is not configured)success
transcriptr   errorAudio file not found: languagemodelzhermes-cmd-stt--prefixztranscript.)
input_pathr  
output_dirr   r0  r1  z0Transcribing %s via command STT provider '%s'...zSTT command provider 'z' timed out after gsNzstderr: zstdout: z; zno command outputz' exited with code z: z
' failed: z7Transcribed %s via command STT provider '%s' (%d chars)Tr,  r-  r   )r[   rI   rk   r   
expanduserrX   r   r   DEFAULT_COMMAND_STT_LANGUAGEtempfileTemporaryDirectoryresolver  r   rx   infor   r  r   r  r  r   r   r   r   r  r'  r%  OSErrorr   )r(  r)  r   rL   r*  r   audior   r   r0  r1  tmpdirr  r   r   resultr   detail_partsdetailtranscript_texts                       r   _transcribe_command_sttrG  `  s<   4 6::i006B77==?? 
%OmOOO	
 
 	
 OO&&((E<<>> 
%9i99	
 
 	
 'v..G26::M

: 	(>>*%%	(' 
 7fjj117RE?
(0R-0R0R0RSSS 6	W]v,,)F})F)FFK!%--//22";//!+"455'MMU L 33C\RRGKKB
M  )'7;;, 	 	 	$"$ -( ( ("( ( (   %6	 6	 6	 6	 6	 6	 6	 6	6 0   !: I ''(G3:3C3C3E3E(G(GHHH: I ''(G3:3C3C3E3E(G(GHHH<00G4G$"$ -6 6 6>6 6-36 6      E6	 6	 6	 6	 6	 6	 6	 6	6"
":!4"m# #     $"$ - XX	      c6	 6	 6	 6	 6	 6	 6	 6	`  [6	 6	 6	 6	 6	 6	 6	 6	 6	 6	 6	 6	 6	 6	 6	p  
 
 
%LmLLsLL	
 
 	
 	
 	
 	
 	
 	

 KKA
M3#7#7  
 %!  s   ,L B(L
.F?>L
?J)L
L +J)9BJ$J)L
L $J))L
-KL

K:K5#K:$L
(L 5K::L
>L 
LL LL 
L7 L2,L72L7c                 "   t          |           sdS d| v }|                     dt                    }|r~|dk    rEt          rdS t	                      rdS t                      rdS t                              d           dS |dk    rOt	                      rdS t          rt                              d           dS t                              d           dS |dk    r4t          rt          d	          rdS t                              d
           dS |dk    r3t          rt                      rdS t                              d           dS |dk    rt                              d           dS |dk    rAddlm}  |                                d          rdS t                              d           dS |S t          rdS t	                      rdS t                      rdS t          r+t          d	          rt                              d           dS t          r*t                      rt                              d           dS 	 ddlm}  |                                d          rt                              d           dS n# t          $ r Y nw xY wdS )u   Determine which STT provider to use.

    When ``stt.provider`` is explicitly set in config, that choice is
    honoured — no silent cloud fallback.  When no provider is configured,
    auto-detect tries: local > groq (free) > openai (paid).
    r   r   r&   r   zhSTT provider 'local' configured but unavailable (install faster-whisper or set HERMES_LOCAL_STT_COMMAND)z9Local STT command unavailable, using local faster-whisperz7STT provider 'local_command' configured but unavailabler   GROQ_API_KEYz7STT provider 'groq' configured but GROQ_API_KEY not setr$   z9STT provider 'openai' configured but no API key availabler   u
  STT provider 'mistral' (Voxtral Transcribe) is temporarily disabled — `mistralai` PyPI package is quarantined (malicious 2.4.6 release on 2026-05-12). Falling back to another provider. Set stt.provider in config.yaml to 'local' or 'openai' to silence this warning.r   r   resolve_xai_http_credentialsapi_keyzBSTT provider 'xai' configured but no xAI credentials are availablez.No local STT available, using Groq Whisper APIz0No local STT available, using OpenAI Whisper APIz.No local STT available, using xAI Grok STT API)rO   rI   DEFAULT_PROVIDER_HAS_FASTER_WHISPERrs   r   rx   ry   r?  _HAS_OPENAIr   rT   tools.xai_httprK  rJ   )rL   explicitr   rK  s       r   _get_providerrR    s/    *%% vZ'H~~j*:;;H  Bw" w!## '&$&& wNNK   6&&!## '&" WXXXwNNI   6v }^<< vNNI   6x  8::  xNNK   6y  
 NN7   6uCCCCCC++--11)<< uNNT   6  w  w }^44 DEEEv 022 FGGGx??????''))--i88 	KKHIII5	    6s   ?=I? ?
JJr1  r0  r1  r0  c                   |sdS |                                                                 }|t          v s|dk    rdS |t          t	          ||                    rdS 	 ddlm} ddlm}  |              ||          }| |d            ||          }n3# t          $ r&}	t                              d|	           Y d}	~	dS d}	~	ww xY w|dS 	 |                                }
n7# t          $ r*}	t                              d	||	d
           d}
Y d}	~	nd}	~	ww xY w|
s&t                              d|           ddd| d|dS t                              d|           	 |                    | ||          }nB# t          $ r5}	t                              d||	d
           ddd| d|	 |dcY d}	~	S d}	~	ww xY wt!          |t"                    sddd| d|dS |                    d|           |S )u  Route the call to a plugin-registered transcription provider, or
    return None.

    Returns the transcribe-response dict on dispatch, or ``None`` to
    fall through to the legacy "No STT provider available" error path.

    Resolution invariants enforced here:

    1. Built-in provider names short-circuit — never reach the plugin
       registry. The caller (``transcribe_audio``) handles ``local``,
       ``groq``, ``openai``, etc. via its existing elif chain; this
       function defensively rejects those names so a plugin can't be
       silently dispatched under a built-in name even if it somehow
       slipped past the registry's built-in shadow guard.
    2. Same-name command-type provider declared under
       ``stt.providers.<name>: type: command`` wins over a plugin. The
       caller short-circuits to the command runner before reaching us,
       but we re-verify here so a refactor of the caller can't silently
       break the invariant (matches TTS PR #17843 precedence rule).
    3. Plugin dispatch fires only when ``provider`` matches a
       registered :class:`TranscriptionProvider` whose ``name`` equals
       the configured value. Unknown names with no plugin registered
       return None (caller surfaces the legacy "No STT provider"
       message).
    4. Availability gating: when the matched plugin reports
       ``is_available() == False`` (missing API key, missing optional
       SDK, etc.) this returns an error envelope identifying the
       plugin as unavailable — **not** ``None`` — because the user
       explicitly opted into this plugin via ``stt.provider`` and the
       generic fallthrough message would be misleading.

    Provider exceptions are caught and converted into the standard
    error envelope (matches the legacy built-in error shapes — the
    gateway/CLI caller already expects ``{success: False, error:
    "...", transcript: ""}`` on failure).
    Nr   r   )get_provider)_ensure_plugins_discoveredT)forcez2STT plugin dispatch skipped (discovery failed): %suN   STT plugin provider '%s' is_available() raised: %s — treating as unavailableexc_infoFzRSTT plugin provider '%s' reports not available; returning unavailability envelope.ri   zSTT plugin 'uY   ' is not available — check that its required credentials / dependencies are configured.)r,  r-  r.  r   z-Transcribing with plugin STT provider '%s'...rS  z#STT plugin provider '%s' raised: %sz
' raised: z' returned a non-dict resultr   )r   rk   r   r   r   agent.transcription_registryrU  hermes_cli.pluginsrV  rJ   rx   r   is_availablery   r?  
transcriber   r   
setdefault)r(  r   rL   r1  r0  r   rU  rV  plugin_providerr   	availablerC  s               r   _dispatch_to_plugin_providerra  U  s#   X  t
..


 
 
"
"C
###sf}}t "A&z377# # t======AAAAAA""$$$&,s++" '&T2222*l3//O   I3OOOttttt t#0022		   &'*C$ 	 	
 	
 	
 						  
'(+	
 	
 	

 Fs F F F 
 
 	
 KK?EEE
 ++ , 
 

  	
 	
 	
13d 	 	
 	
 	
 8C88388	
 
 	
 	
 	
 	
 	
 	
		
 fd## 
ECEEE	
 
 	
 j#&&&MsN   :B 
C	#CC	C& &
D0 DD!E: :
F9*F4.F94F9c           
      T   t          |           }t          j                            |          r	ddd|  dS |                                s	ddd|  dS |                                s	ddd|  dS |j                                        t          vr6ddd|j         dd		                    t          t                               dS 	 |                                j        }|t          k    rddd
|dz  ddt          dz  dddS n # t          $ r}ddd| dcY d}~S d}~ww xY wdS )z>Validate the audio file.  Returns an error dict or None if OK.Fri   zPath is a symbolic link: r,  r-  r.  r/  zPath is not a file: zUnsupported format: z. Supported: z, zFile too large: i   z.1fzMB (max z.0fzMB)zFailed to access file: N)r   r   pathislinkrX   is_filesuffixr   SUPPORTED_FORMATSr   sortedstatst_sizeMAX_FILE_SIZEr@  )r(  
audio_path	file_sizees       r   _validate_audio_filerp    s   iJ	w~~j!! f =dYb=d=deee c =aV_=a=abbb a =_T]=_=_```  (999rJ,=rrDIIV\]nVoVoLpLprr
 
 	

	\OO%%-	}$$  uI,CuuuQ^bkQluuuu   %  \ \ \ =ZWX=Z=Z[[[[[[[[\ 4s   	=D 
D%D D% D%)	libcublaslibcudnn	libcudartzcannot be loadedzcannot open shared objectzno kernel image is availablezno CUDA-capable devicez#CUDA driver version is insufficientr   c                 b    t          |           t          fdt          D                       S )ag  Heuristic: is this exception a missing/broken CUDA runtime library?

    ctranslate2 raises plain RuntimeError with messages like
    ``Library libcublas.so.12 is not found or cannot be loaded``.  We want to
    catch missing/unloadable shared libs and driver-mismatch errors, NOT
    legitimate runtime failures ("CUDA out of memory", model bugs, etc.).
    c              3       K   | ]}|v V  	d S r   rS   )r   markermsgs     r   r   z-_looks_like_cuda_lib_error.<locals>.<genexpr>)  s'      CCv}CCCCCCrP   )r[   any_CUDA_LIB_ERROR_MARKERS)r   rw  s    @r   _looks_like_cuda_lib_errorrz     s4     c((CCCCC+BCCCCCCrP   c                     ddl m} 	  || dd          S # t          $ rC}t          |          s t                              d|            || dd          cY d}~S d}~ww xY w)	uc  Load faster-whisper with graceful CUDA → CPU fallback.

    faster-whisper's ``device="auto"`` picks CUDA when the ctranslate2 wheel
    ships CUDA shared libs, even on hosts where the NVIDIA runtime
    (``libcublas.so.12`` / ``libcudnn*``) isn't installed — common on WSL2
    without CUDA-on-WSL, headless servers, and CPU-only developer machines.
    On those hosts the load itself sometimes succeeds and the dlopen failure
    only surfaces at first ``transcribe()`` call.

    We try ``auto`` first (fast CUDA path when it works), and on any CUDA
    library load failure fall back to CPU + int8.
    r   WhisperModelautodevicecompute_typeu   faster-whisper CUDA load failed (%s) — falling back to CPU (int8). Install the NVIDIA CUDA runtime (libcublas/libcudnn) to use GPU.cpuint8N)r#   r}  rJ   rz  rx   ry   )rt   r}  r   s      r   _load_local_whisper_modelr  ,  s     ,+++++
K|JvFKKKK K K K)#.. 	O	
 	
 	

 |Ju6JJJJJJJJJKs    
A#8AA#A#c                    t           st                      sddddS 	 t          t          |k    r,t                              d|           t          |          a|at                                          di                               d          pt          j
        t                    pd}d	d
i}|r||d<   	 t          j        | fi |\  }}d                    d |D                       }n# t          $ r}t          |          s t                              d|           dadaddlm}  ||dd          a|at          j        | fi |\  }}d                    d |D                       }Y d}~nd}~ww xY wt                              dt'          |           j        ||j        |j                   d|ddS # t          $ r0}	t                              d|	d           ddd|	 dcY d}	~	S d}	~	ww xY w)z.Transcribe using faster-whisper (local, free).Fri   zfaster-whisper not installedrc  NzELoading faster-whisper model '%s' (first load downloads the model)...r&   r0  	beam_sizer    c              3   H   K   | ]}|j                                         V  d S r   r  rk   r   segments     r   r   z$_transcribe_local.<locals>.<genexpr>b  0      !O!O7',"4"4"6"6!O!O!O!O!O!OrP   ul   faster-whisper CUDA runtime failed mid-transcribe (%s) — evicting cached model and retrying on CPU (int8).r   r|  r  r  r  c              3   H   K   | ]}|j                                         V  d S r   r  r  s     r   r   z$_transcribe_local.<locals>.<genexpr>v  r  rP   z;Transcribed %s via local whisper (%s, lang=%s, %.1fs audio)Tr9  zLocal transcription failed: %srX  Local transcription failed: )rN  r   rC   rD   rx   r?  r  rK   rI   r   r   LOCAL_STT_LANGUAGE_ENVr]  r   rJ   rz  ry   r#   r}  r   r   r0  durationr.  )
r(  rt   _forced_langtranscribe_kwargssegmentsr?  r-  r   r}  ro  s
             r   _transcribe_localr  G  s     a$&& 	a$BA_```2a#4
#B#BKK_aklll4Z@@L * ""7B//33J?? y/00 	
 )!, 	9,8j)	P)4YTTBSTTNHd!O!Oh!O!O!OOOJJ 	P 	P 	P .c22 NND  
  L $333333'<
5vVVVL *)4YTTBSTTNHd!O!Oh!O!O!OOOJJJJJJ'	P* 	IOO *dmT]	
 	
 	

  zwOOO a a a5q4HHH =_\]=_=_````````asJ   BF< 74C, +F< ,
E96A9E4/F< 4E99AF< <
G6%G1+G61G6work_dirc                 $   t          |           }|j                                        t          v r| dfS t	                      }|sdS t
          j                            ||j         d          }|dd| |g}	 t          j
        |ddd           |dfS # t          j        $ rn}|j                                        p'|j                                        pt          |          }t                               d| |           dd	| fcY d}~S d}~ww xY w)
z.Normalize audio for local CLI STT when needed.N)NzOLocal STT fallback requires ffmpeg for non-WAV inputs, but ffmpeg was not foundr<   z-yz-iTcheckcapture_outputr  z#ffmpeg conversion failed for %s: %sz'Failed to convert audio for local STT: )r   rg  r   LOCAL_NATIVE_AUDIO_FORMATSrd   r   rd  r   stemr   r   r  r   rk   r   r[   rx   r.  )r(  r  rm  rb   converted_pathr   ro  detailss           r   _prepare_local_audior    s3   iJ  $>>>$ ""F gffW\\(z,D,D,DEENtT9n=GIwd4dKKKKt##( I I I(..""@ahnn&6&6@#a&&:IwOOOHwHHHHHHHHHIs   6B D!A#D
D
Dc           	         t                      }|sddt           ddS t                                          di                               d          pt	          j        t                    pt          }t          |          }	 t          j
        d          5 }t          | |          \  }}|rdd|dcd	d	d	           S |                    t          j        |          t          j        |          t          j        |          t          j        |          
          }t          t	          j        t          d                                                    }	|	rt#          j        |dddd           n*t#          j        t          j        |          ddd           t)          t+          |                              d                    }
|
sddddcd	d	d	           S |
d                             d                                          }t0                              dt+          |           j        |t7          |                     d|ddcd	d	d	           S # 1 swxY w Y   d	S # t8          $ r}dddt           d| dcY d	}~S d	}~wt"          j        $ rp}|j                                        p'|j                                        ptA          |          }t0          !                    d| |           ddd| dcY d	}~S d	}~wtD          $ r0}t0          !                    d|d           ddd| dcY d	}~S d	}~ww xY w)zNRun the configured local STT command template and read back a .txt transcript.Fri   z5 not configured and no local whisper binary was foundrc  r&   r0  zhermes-local-stt-r3  N)r5  r6  r0  r1  T)r  r  r  r  r  z*.txtzALocal STT command completed but did not produce a .txt transcriptr   r  r  z3Transcribed %s via local STT command (%s, %d chars)r   r9  zInvalid z  template, missing placeholder: z#Local STT command failed for %s: %szLocal STT failed: z7Unexpected error during local command transcription: %srX  r  )#rq   rj   rK   rI   r   r   r  DEFAULT_LOCAL_STT_LANGUAGEr~   r<  r=  r  r   rl   rm   r   rk   r   r   splitri  r   globr!  rx   r?  r   r   KeyErrorr  r   r   r[   r.  rJ   )r(  rt   r   r0  normalized_modelr6  prepared_input
prep_errorr   	use_shell	txt_filesrF  ro  r  s                 r   _transcribe_local_commandr    si   244 
(___	
 
 	
 	w++//
;; 	&9+,,	&% 
 6jAA1a(0CDDD "	a
)=i)T)T&NJ Q#(ZPP"	a "	a "	a "	a "	a "	a "	a "	a
 '-- ;~66 ;z22X..k"233	 .  G RY'<bAAGGIIJJI awd$tZ^_____u{7334PT[_```` tJ//44W==>>I $"$` +"	a "	a "	a "	a "	a "	a "	a "	a6 (l44g4FFLLNNOKKEY$ O$$	    $?P_``E"	a "	a "	a "	a "	a "	a "	a "	a "	a "	a "	a "	a "	a "	a "	a "	a "	a "	aH  
 
 
Z 5ZZWXZZ
 
 	
 	
 	
 	
 	
 	

 ( ] ] ](..""@ahnn&6&6@#a&&:IwOOO =[RY=[=[\\\\\\\\ a a aNPQ\`aaa =_\]=_=_````````as   I- I 4I- DI I- $A/I I-  I$$I- 'I$(I- -
M7JMMA%L
M
M%M<MMc                 &   t          d          }|sddddS t          sddddS |t          v r(t                              d|t
                     t
          }	 dd	lm}m}m	}m
}  ||t          d
d          }	 t          | d          5 }|j        j                            ||d          }	ddd           n# 1 swxY w Y   t!          |	                                          }
t                              dt%          |           j        |t)          |
                     d|
ddt+          |dd          }t-          |          r |             S S # t+          |dd          }t-          |          r |             w w xY w# t.          $ r ddd|  dcY S |$ r}ddd| dcY d}~S d}~w|$ r}ddd| dcY d}~S d}~w|$ r}ddd| dcY d}~S d}~wt0          $ r0}t                              d|d           ddd| dcY d}~S d}~ww xY w)z8Transcribe using Groq Whisper API (free tier available).rI  Fri   zGROQ_API_KEY not setrc  openai package not installedz(Model %s not available on Groq, using %sr   OpenAIAPIErrorAPIConnectionErrorAPITimeoutError   rL  base_urlr   max_retriesrbr  r1  fileresponse_formatNz*Transcribed %s via Groq API (%s, %d chars)Tr   r9  closePermission denied: Connection error: Request timeout: API error: zGroq transcription failed: %srX  Transcription failed: )r   rO  rv   rx   r?  DEFAULT_GROQ_STT_MODELr$   r  r  r  r  r0   openrA  transcriptionscreater[   rk   r   r   r   r  callablePermissionErrorrJ   r.  )r(  rt   rL  r  r  r  r  client
audio_filetranscriptionrF  r  ro  s                r   _transcribe_groqr    sh   N++G U =STTT ] =[\\\ ]"">
Lbccc+
[PPPPPPPPPPPP-YZ[[[	i&& * & ; B B$#$* !C ! !               "-006688OKKDi-z3;O;OQ Q Q  $?PVWWFGT22E  FGT22E   ` ` ` =^S\=^=^_____ W W W =URS=U=UVVVVVVVV V V V =TQR=T=TUUUUUUUU P P P =N1=N=NOOOOOOOO [ [ [4a$GGG =YVW=Y=YZZZZZZZZ[s    F  8E #B7+E 7B;;E >B;?A%E $*F  -E==F   HHF'!H'H/F=7H=HGHH %HHHc                 T   	 t                      \  }}n*# t          $ r}ddt          |          dcY d}~S d}~ww xY wt          sddddS |t          v r(t
                              d|t                     t          }	 ddlm	}m
}m}m}  |||d	d
          }		 t          | d          5 }
|	j        j                            ||
|dk    rdnd          }ddd           n# 1 swxY w Y   t#          |          }t
                              dt%          |           j        |t)          |                     d|ddt+          |	dd          }t-          |          r |             S S # t+          |	dd          }t-          |          r |             w w xY w# t.          $ r ddd|  dcY S |$ r}ddd| dcY d}~S d}~w|$ r}ddd| dcY d}~S d}~w|$ r}ddd| dcY d}~S d}~wt0          $ r0}t
                              d|d           ddd| dcY d}~S d}~ww xY w)z+Transcribe using OpenAI Whisper API (paid).Fri   rc  Nr  z*Model %s not available on OpenAI, using %sr   r  r  r  r  r*   r  r   r  z,Transcribed %s via OpenAI API (%s, %d chars)Tr$   r9  r  r  r  r  r  zOpenAI transcription failed: %srX  r  )rR   r   r[   rO  rw   rx   r?  DEFAULT_STT_MODELr$   r  r  r  r  r  rA  r  r  _extract_transcript_textr   r   r   r  r  r  rJ   r.  )r(  rt   rL  r  r   r  r  r  r  r  r  r  rF  r  ro  s                  r   _transcribe_openair    s   
?AA 
 
 
XX
 
 	
 	
 	
 	
 	
 	

  ] =[\\\ [  @*N_```&
[PPPPPPPPPPPP(BTUVVV	i&& * & ; B B$#.8K.G.GFFV !C ! !               7}EEOKKFi-z3;O;OQ Q Q  $?PXYYFGT22E  FGT22E   ` ` ` =^S\=^=^_____ W W W =URS=U=UVVVVVVVV V V V =TQR=T=TUUUUUUUU P P P =N1=N=NOOOOOOOO [ [ [6DIII =YVW=Y=YZZZZZZZZ[s    
;6;;=F E' )+C E'  C$$E' 'C$(AE' ;*F '-FF H',H'0F>8H'>H'GH'H'G*$H'*H'7%H"H'"H'c           	         t          d          }|sddddS 	 ddlm}  ||          5 }t          | d	          5 }|j        j                            ||t          |           j        d
          }ddd           n# 1 swxY w Y   t          |          }t                              dt          |           j        |t          |                     d|ddcddd           S # 1 swxY w Y   dS # t          $ r ddd|  dcY S t          $ rB}t                              d|d           dddt!          |          j         dcY d}~S d}~ww xY w)zTranscribe using Mistral Voxtral Transcribe API.

    Uses the ``mistralai`` Python SDK to call ``/v1/audio/transcriptions``.
    Requires ``MISTRAL_API_KEY`` environment variable.
    MISTRAL_API_KEYFri   zMISTRAL_API_KEY not setrc  r   )Mistral)rL  r  )r&  	file_name)r1  r  Nz-Transcribed %s via Mistral API (%s, %d chars)Tr   r9  r  z Mistral transcription failed: %srX  zMistral transcription failed: )r   mistralai.clientr  r  rA  r  completer   r   r  rx   r?  r   r  rJ   r.  r   __name__)	r(  rt   rL  r  r  r  rC  rF  ro  s	            r   _transcribe_mistralr  S  s[    -..G X =VWWWr,,,,,,WW%%% 	[i&& *4==$%/d9oo>RSS >                 7v>>OKK?Y$j#o2F2F    $?PYZZ	[ 	[ 	[ 	[ 	[ 	[ 	[ 	[ 	[ 	[ 	[ 	[ 	[ 	[ 	[ 	[ 	[ 	[  ` ` ` =^S\=^=^_____ r r r7TJJJ =p^bcd^e^e^n=p=pqqqqqqqqrsj   C4 C'7A?3C'?B	C'B	AC'C4 'C++C4 .C+/C4 4E		E7E	EEc           	      "   ddl m}  |            }t          |                    d          pd                                          }|sddddS t                      }|                    di           }t          |                    d	          p*t          d
          p|                    d	          pt                                                                        d          }t          |                    d          pt          j
        d          pt                                                    }t          |                    dd                    }	t          |                    dd                    }
	 ddl}ddl m} i }|r||d<   |	rd|d<   |
rd|d<   t          | d          5 }|                    | dd|  |            ddt#          |           j        |fi|d          }ddd           n# 1 swxY w Y   |j        dk    rd}	 |                                }|                    di                               dd          p|j        dd         }n# t,          $ r |j        dd         }Y nw xY wddd|j         d | dS |                                }|                    d!d                                          }|sddd"dS t.                              d#t#          |           j        |                    d|          |                    d$d          t3          |                     d|dd%S # t4          $ r ddd&|  dcY S t,          $ r0}t.                              d'|d(           ddd)| dcY d}~S d}~ww xY w)*zTranscribe using xAI Grok STT API.

    Uses the ``POST /v1/stt`` REST endpoint with multipart/form-data.
    Supports Inverse Text Normalization, diarization, and word-level timestamps.
    Requires ``XAI_API_KEY`` environment variable.
    r   rJ  rL  ri   FzRNo xAI credentials found. Configure xAI OAuth in `hermes model` or set XAI_API_KEYrc  r   r  r2   /r0  r/   r   TdiarizeN)hermes_xai_user_agenttruer  z/sttzBearer )Authorizationz
User-Agentr  x   )headersfilesdatar      r.  messager   zxAI STT API error (HTTP z): r  z!xAI STT returned empty transcriptz@Transcribed %s via xAI Grok STT (lang=%s, %.1fs audio, %d chars)r  r9  r  z xAI STT transcription failed: %srX  zxAI STT transcription failed: )rP  rK  r[   rI   rk   rK   r   r2   rstripr   r   r  r   requestsr  r  postr   r   status_coder   r  rJ   rx   r?  r   r  r.  )r(  rt   rK  credsrL  rL   
xai_configr  r0  
use_formatuse_diarizer  r  r  r  responserE  err_bodyrC  rF  ro  s                        r   _transcribe_xair  z  s    <;;;;;((**E%))I&&,"--3355G 
i
 
 	
 "##Jr**Jz"" 	+,,	99Z  	 	 
 eggffSkk  z"" 	&9011	&%  egg	  !$!?!?@@J!*..E"B"BCCK?c888888! 	('D 	$#DN 	%$DO)T"" 	j}}!!!%8w%8%8"7"7"9"9 
 T)__1:>  %  H	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 3&&F-#==??!gr2266y"EE\W[X[W[I\ - - -!tt,- ! UH4HUUVUU    **VR006688 	  <   	NOO JJz8,,JJz1%%  	
 	
 	
  ERRR ` ` ` =^S\=^=^_____ c c c7TJJJ =a^_=a=abbbbbbbbcs|   ;1M  ,AG:.M  :G>>M  G>M  AI!  M  !I=:M  <I==M  AM  A*M   N	N%N	N	Nc                    t          |           }|r|S t                      }t          |          sddddS t          |          }|dk    rP|                    di           }t          |p|                    dt                              }t          | |          S |dk    rP|                    di           }t          |p|                    dt                              }t          | |          S |dk    r|pt          }t          | |          S |d	k    rC|                    d	i           }|p|                    dt                    }t          | |          S |d
k    rC|                    d
i           }|p|                    dt                    }t          | |          S |dk    r|pd}t!          | |          S t#          ||          }	|	t%          | ||	||          S t'          |                    |          t(                    r|                    |i           ni }
|
                    d          }|p|
                    d          }t+          | ||||          }||S dddt,           ddS )a  
    Transcribe an audio file using the configured STT provider.

    Provider priority:
      1. User config (``stt.provider`` in config.yaml)
      2. Auto-detect: local faster-whisper (free) > Groq (free tier) > OpenAI (paid)

    Args:
        file_path: Absolute path to the audio file to transcribe.
        model:     Override the model. If None, uses config or provider default.

    Returns:
        dict with keys:
          - "success" (bool): Whether transcription succeeded
          - "transcript" (str): The transcribed text (empty on failure)
          - "error" (str, optional): Error message if success is False
          - "provider" (str, optional): Which provider was used
    Fri   z4STT is disabled in config.yaml (stt.enabled: false).rc  r&   r1  r   r   r$   r   r   zgrok-sttN)r*  r0  rS  zZNo STT provider available. Install faster-whisper for free local transcription, configure a   or install a local whisper CLI, set GROQ_API_KEY for free Groq Whisper, set MISTRAL_API_KEY for Mistral Voxtral Transcribe, configure xAI OAuth or set XAI_API_KEY for xAI Grok STT, or set VOICE_TOOLS_OPENAI_KEY or OPENAI_API_KEY for the OpenAI Whisper API.)rp  rK   rO   rR  rI   r|   rz   r  r~   r  r  r  r  r  DEFAULT_MISTRAL_STT_MODELr  r  r   rG  r   r   ra  rj   )r(  r1  r.  rL   r   	local_cfgrt   
openai_cfgmistral_cfgcommand_provider_config
plugin_cfgplugin_languageplugin_modelplugin_results                 r   transcribe_audior    s   ( !++E  "##J*%% 
K
 
 	
 Z((H7NN7B//	+@Y]]7,?@@
 

 !J777?""NN7B//	3@Y]]7,?@@
 

 )J???644
	:6668^^Hb11
HjnnW6GHH
!)Z8889 nnY33Qkoog7PQQ
"9j9995(j
y*555 C8ZXX*&# 
 
 
 	
* 2<JNN8<T<TVZ1[1[c"---acJ nnZ00O3JNN733L0   M   <(=< < <	
 
 
rP   c                     t                      } |                     di           }|                    dd          }|                    dd          }|r||pt          fS t                      }|r	|t          fS t	          d          }|4d}t                      r|dt          d	          z   z  }t          |          |j        t          |j
                            d
           d
d          fS )z@Return direct OpenAI audio config or a managed gateway fallback.r$   rL  ri   r  zopenai-audioNzUNeither stt.openai.api_key in config nor VOICE_TOOLS_OPENAI_KEY/OPENAI_API_KEY is setz. z&managed OpenAI audio for transcriptionr  v1)rK   rI   OPENAI_BASE_URLr   r	   r
   r   r   nous_user_tokenr   gateway_originr  )rL   r  cfg_api_keycfg_base_urldirect_api_keymanaged_gatewayr  s          r   rR   rR   a  s   !##J"--J..B//K>>*b11L >\<_==133N /..2>BBOi%'' 	7< G !!!*G)0055888$- -  rP   r  c                    t          | t                    r|                                 S t          | d          r9t	          | d          }t          |t                    r|                                S t          | t
                    r>|                     d          }t          |t                    r|                                S t          |                                           S )zBNormalize text and JSON transcription responses to a plain string.r  )r   r[   rk   hasattrr  r   rI   )r  r   s     r   r  r    s    -%% %""$$$}f%% !v..eS!! 	!;;== -&& !!!&))eS!! 	!;;== }##%%%rP   r   )q__doc__loggingr   rl   r\   r   r<  pathlibr   typingr   r   r   urllib.parser   utilsr   tools.managed_tool_gatewayr	   tools.tool_backend_helpersr
   r   r   	getLoggerr  rx   r   r   r   r   r[   r   r"   rN  rO  _HAS_MISTRALrM  rz   r  r   r  r  r  rj   r  rW   r0   r  r2   rh  r  rl  rv   rw   rC   object__annotations__rD   r   rK   rO   rT   r`   rd   rg   rq   rs   r|   r~   r   	frozensetr   r   r;  r   r   r   r   r   r   r   r   r   r   r   intr   r   r   r  r
  r  r  r'  rG  rR  ra  rp  ry  BaseExceptionrz  r  r  tupler  r  r  r  r  r  r  rR   r  rS   rP   r   <module>r     s
    6  				              & & & & & & & & & &             ! ! ! ! ! ! C C C C C C          
	8	$	$/ / / /$      I I I I I I &o&677 oh''{++   ! BI0+>> "#35MNN %BI&9;PQQ 2 4 ? 	/+KLL")13NOO29/1FGG hhh 666   MLLZZZ "&hv % % %#' 8C= ' ' '$    2 2x~ 2 2 2 2 24    %c %hsm % % % %"Xc] " " " "#hsm # # # #Xc]    5D 5 5 5 5x}     *.x} . . . . .t    4 "	 # # #   < '* ## $) !&Y'D'D'DEE 8c3h 8s 8tCH~ 8 8 8 8S#X
 
#s(^   <>DcN >t > > > >S#X d38n   * DcN         htCH~.F RV    	T#s(^ 	 	 	 	 	[4S> [c [ [ [ [s c hsm    @# hsm PS    (%%sCx.% 	% % % %P=j.> =4 = = = =@(Qc (QE (Qj6Q (Q (Q (Q (QV$  # #    F %)~ ~~~ cN~ S#X	~
 SM~ 
#s(^~ ~ ~ ~Bld ls l l l ln ,0L
  "L L LLL c3h(L
 C=L smL d38nL L L LhC HT#s(^,D    V	 	DM 	Dd 	D 	D 	D 	DK# K K K K6:a :a# :a$sCx. :a :a :a :azIC I3 I5#PXY\P]A];^ I I I I,Ea Ea# Ea$sCx. Ea Ea Ea EaX-[ -[ -[c3h -[ -[ -[ -[h2[# 2[3 2[4S> 2[ 2[ 2[ 2[rr3 rC rDcN r r r rNccs cc ccS#X cc cc cc ccVy y yHSM yT#s(^ y y y yxU38_    <&C &C & & & & & &rP   