§
    øžyj,  ã                  óú   — d Z ddlmZ ddlZddlZddlmZ ddlmZm	Z	 ddl
mZ  ej        e¦  «        ZdZdZh d	£Zd
dœd-d„Z	 d.d
dœd/d„Zd0d„Zd0d„Zd0d„Zd1d„Zddœd2d!„Zd3d#„Z	 	 d4d5d(„Zd6d)„Zd7d+„Zd1d,„ZdS )8z*Shared helpers for tool backend selection.é    )ÚannotationsN)ÚPath)ÚAnyÚDict)Úis_truthy_valueÚlocalÚauto>   r	   ÚdirectÚmanagedF©Úforce_freshr   ÚboolÚreturnc                ó„   — 	 ddl m} | r |d¬¦  «        }n
 |¦   «         }|j        sdS |j        S # t          $ r Y dS w xY w)u²  Return True when the user is entitled to the Nous Tool Gateway.

    Entitlement is paid Nous Portal service access OR a live free tool pool
    (``tool_gateway_entitled``). Per-category coverage (the pool funds image but
    not video, etc.) is narrowed by callers via ``tool_gateway_entitled_for``;
    this coarse gate only answers "is any managed tool usable at all".

    Tool Gateway availability fails closed on unknown/error entitlement.  We
    intentionally catch all exceptions and return False â€” never block startup.
    ``force_fresh=True`` is for interactive configuration flows that should
    reflect a just-purchased subscription, credits, or pool grant immediately.
    r   )Úget_nous_portal_account_infoTr   F)Úhermes_cli.nous_accountr   Ú	logged_inÚtool_gateway_entitledÚ	Exception)r   r   Úaccount_infos      ú@/home/ragecks/.hermes/hermes-agent/tools/tool_backend_helpers.pyÚmanaged_nous_tools_enabledr      s   € ðØHÐHÐHÐHÐHÐHàð 	:Ø7Ð7ÀDÐIÑIÔIˆLˆLà7Ð7Ñ9Ô9ˆLØÔ%ð 	Ø�5ØÔ1Ð1øÝð ð ð Øˆuˆuðøøøs   ‚&1 ª1 ±
?¾?úthe Nous Tool GatewayÚ
capabilityÚstrc               óz   — 	 ddl m}m}  ||¬¦  «        } ||| ¬¦  «        }|r|S n# t          $ r Y nw xY w| › d�S )zHReturn account-aware guidance for an unavailable Nous Tool Gateway path.r   )Ú&format_nous_portal_entitlement_messager   r   )r   zY is unavailable. Run `hermes model` to refresh your Nous Portal login and billing status.)r   r   r   r   )r   r   r   r   r   Úmessages         r   Ú%nous_tool_gateway_unavailable_messager   /   s®   € ðð	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
ð
 4Ð3ÀÐLÑLÔLˆØ8Ð8ØØ!ð
ñ 
ô 
ˆð ð 	ØˆNð	øåð ð ð Øˆðøøøð ð 	0ð 	0ð 	0ðs   ‚$( ¨
5´5Úvalueúobject | Nonec                óˆ   — t          | pt          ¦  «                             ¦   «                              ¦   «         }|pt          S )z)Return a normalized browser provider key.)r   Ú_DEFAULT_BROWSER_PROVIDERÚstripÚlower)r    Úproviders     r   Ú normalize_browser_cloud_providerr'   J   s9   € å�5Ð5Õ5Ñ6Ô6×<Ò<Ñ>Ô>×DÒDÑFÔF€HØÐ0Õ0Ð0ó    c                óš   — t          | pt          ¦  «                             ¦   «                              ¦   «         }|t          v r|S t          S )z=Return the requested modal mode when valid, else the default.)r   Ú_DEFAULT_MODAL_MODEr$   r%   Ú_VALID_MODAL_MODES)r    Úmodes     r   Úcoerce_modal_moder-   P   sD   € åˆuÐ+Õ+Ñ,Ô,×2Ò2Ñ4Ô4×:Ò:Ñ<Ô<€DØÕ!Ð!Ð!ØˆÝÐr(   c                ó    — t          | ¦  «        S )z)Return a normalized modal execution mode.)r-   )r    s    r   Únormalize_modal_moder/   X   s   € å˜UÑ#Ô#Ð#r(   c                 óö   — 	 t          j        ¦   «         dz                       ¦   «         } n# t          t          f$ r d} Y nw xY wt          t          j        d¦  «        rt          j        d¦  «        p| ¦  «        S )z?Return True when direct Modal credentials/config are available.z.modal.tomlFÚMODAL_TOKEN_IDÚMODAL_TOKEN_SECRET)r   ÚhomeÚexistsÚPermissionErrorÚOSErrorr   ÚosÚgetenv)Úmodal_file_existss    r   Úhas_direct_modal_credentialsr:   ]   s‹   € ð"Ý!œY™[œ[¨=Ñ8×@Ò@ÑBÔBÐÐøÝ�WÐ%ð "ð "ð "Ø!ÐÐÐð"øøøåÝ	ŒÐ#Ñ	$Ô	$Ð	H­¬Ð3GÑ)HÔ)Hð 	Øñô ð s   ‚(+ «AÁ A)Úmanaged_enabledÚ
modal_modeÚ
has_directÚmanaged_readyr;   úbool | NoneúDict[str, Any]c               óÒ   — t          | ¦  «        }t          | ¦  «        }|€t          ¦   «         }|dk    o| }|dk    r	|r|rdnd}n|dk    r|rdnd}n|r|rdn|rdnd}||||||dœS )zØResolve direct vs managed Modal backend selection.

    Semantics:
    - ``direct`` means direct-only
    - ``managed`` means managed-only
    - ``auto`` prefers managed when available, then falls back to direct
    Nr   r
   )Úrequested_moder,   r=   r>   Úmanaged_mode_blockedÚselected_backend)r-   r/   r   )r<   r=   r>   r;   rB   Únormalized_moderC   rD   s           r   Úresolve_modal_backend_staterF   i   sÇ   € õ ' zÑ2Ô2€NÝ*¨:Ñ6Ô6€OØÐÝ4Ñ6Ô6ˆà˜)Ò#Ð;¨OÐ(;ð ð ˜)Ò#Ð#Ø(7ÐS¸MÐS˜9˜9ÈtÐÐØ	˜HÒ	$Ð	$Ø'1Ð;˜8˜8°tÐÐà(7Ðo¸MÐo˜9˜9Ð[eÐOoÈxÈxÐkoÐð )ØØ Ø&Ø 4Ø,ðð ð r(   Únamec                óÂ   — 	 ddl m}  || d¦  «        pd                     ¦   «         S # t          $ r, t	          j        | d¦  «        pd                     ¦   «         cY S w xY w)z×Read a credential env var under the active profile secret scope.

    Falls back to a raw read only when ``agent.secret_scope`` cannot be
    imported, so a packaging edge never leaves the caller without a key.
    r   )Ú
get_secretÚ )Úagent.secret_scoperI   r$   r   r7   r8   )rG   rI   s     r   Ú_scoped_credentialrL   �   s€   € ð3Ø1Ð1Ð1Ð1Ð1Ð1à�
˜4 Ñ$Ô$Ð*¨×1Ò1Ñ3Ô3Ð3øÝð 3ð 3ð 3Ý”	˜$ Ñ#Ô#Ð) r×0Ò0Ñ2Ô2Ð2Ð2Ð2ð3øøøs   ‚%( ¨3AÁArJ   Úenv_varÚprovider_idÚconfig_valuec                óL  — t          |pd¦  «                             ¦   «         }|r|S t          | ¦  «        }|r|S 	 ddlm}  |¦   «         rdS n# t
          $ r Y nw xY w|�-t           || ¦  «        pd¦  «                             ¦   «         }nF	 ddlm} t           || ¦  «        pd¦  «                             ¦   «         }n# t          $ r d}Y nw xY w|r|S |sdS 	 ddl	m
} |d|› �fD ]„}	 ||	¦  «        }
|
�|
                     ¦   «         sŒ$|
                     ¦   «         }|€Œ;t          t          |dd¦  «        pt          |d	d¦  «        pd¦  «                             ¦   «         }|r|c S Œ…n4# t
          $ r'}t                               d
|| |¦  «         Y d}~nd}~ww xY wdS )u  Resolve a voice-provider API key. Single owner for STT/TTS key lookup.

    Resolution order (fixes #68003 â€” keys added via ``hermes auth add
    <provider>`` were invisible to the voice tools, which only consulted
    env/.env):

    1. An explicit ``config_value`` from config.yaml, when the caller has one.
    2. The environment / ``~/.hermes/.env``. Under a multiplexed gateway turn
       this reads the active profile's secret scope (authoritative â€” a scope
       miss must NOT borrow another profile's ``os.environ``; see
       ``agent/secret_scope.py``). Outside multiplexing it reads
       ``hermes_cli.config.get_env_value`` (os.environ, then ``.env``),
       matching the tools' historical behaviour exactly.
    3. The credential pool / auth store for ``provider_id`` (``hermes auth
       add <provider_id>``). Skipped under an active multiplex turn, where
       only the profile scope is authoritative for credentials.

    Never raises â€” credential resolution must not hard-fail on a pool or
    config read; returns ``""`` when no key is found anywhere.

    ``env_getter`` lets callers supply their module-level ``get_env_value``
    wrapper (transcription_tools / tts_tool expose one that tests patch);
    when omitted, ``hermes_cli.config.get_env_value`` is used directly.
    rJ   r   )Úis_multiplex_activeN©Úget_env_value)Ú	load_poolzcustom:Úruntime_api_keyÚaccess_tokenz,Could not read %s credential pool for %s: %s)r   r$   rL   rK   rQ   r   Úhermes_cli.configrS   ÚImportErrorÚagent.credential_poolrT   Úhas_credentialsÚpeekÚgetattrÚloggerÚdebug)rM   rN   rO   Ú
env_getterr    ÚkeyrQ   rS   rT   Úpool_keyÚpoolÚentryÚexcs                r   Úresolve_provider_secretre   ž   s�  € õ< �Ð" Ñ#Ô#×)Ò)Ñ+Ô+€EØð Øˆõ
 ˜WÑ
%Ô
%€CØ
ð Øˆ
ð	Ø:Ð:Ð:Ð:Ð:Ð:àÐÑ Ô ð 	ð �2ð		øõ
 ð ð ð Øˆðøøøð ÐÝ�*�*˜WÑ%Ô%Ð+¨Ñ,Ô,×2Ò2Ñ4Ô4ˆˆð	Ø7Ð7Ð7Ð7Ð7Ð7å�m�m GÑ,Ô,Ð2°Ñ3Ô3×9Ò9Ñ;Ô;ˆCˆCøÝð 	ð 	ð 	ØˆCˆCˆCð	øøøà
ð Øˆ
àð Øˆrð
Ø3Ð3Ð3Ð3Ð3Ð3ð %Ð&=°Ð&=Ð&=Ð>ð 	ð 	ˆHØ�9˜XÑ&Ô&ˆDØˆ| 4×#7Ò#7Ñ#9Ô#9ˆ|ØØ—I’I‘K”KˆEØˆ}ØÝÝ˜Ð0°"Ñ5Ô5ð Ý˜5 .°"Ñ5Ô5ðàñô ÷ Še‰gŒgð	 ð
 ð Ø�
�
�
ðð	øõ ð 
ð 
ð 
Ý�ŠØ:ØØØñ		
ô 	
ð 	
ð 	
ð 	
ð 	
ð 	
ð 	
øøøøð
øøøð ˆ2sB   ¼A Á
AÁAÂ2C ÃCÃCÃBE0 Å.E0 Å0
F!Å:FÆF!c                 óB   — t          dd¦  «        pt          dd¦  «        S )u¡  Prefer the voice-tools key, but fall back to the normal OpenAI key.

    Routed through the profile secret scope rather than reading ``os.environ``
    directly: in a multiplex gateway serving several profiles from one
    process, ``os.environ`` reflects whichever profile's ``.env`` happened to
    load at boot, not the profile the current turn belongs to. A raw read here
    lets one profile's TTS reply / voice-note transcription authenticate as â€”
    and get billed against â€” a different profile's OpenAI account. Same
    routing the WeChat send path and ``agent/vertex_adapter`` already use; see
    ``agent/secret_scope.py``.

    Outside a multiplexed turn, ``OPENAI_API_KEY`` additionally falls back to
    the credential pool (``hermes auth add openai-api``) via
    ``resolve_provider_secret`` â€” same #68003 fix as the other voice
    providers. The dedicated voice-tools override remains env/scope-only.
    ÚVOICE_TOOLS_OPENAI_KEYrJ   ÚOPENAI_API_KEYz
openai-api)re   © r(   r   Úresolve_openai_audio_api_keyrj   ÿ   s+   € õ$ 	 Ð 8¸"Ñ=Ô=ð 	CÝ"Ð#3°\ÑBÔBðr(   Úconfig_sectionc                óæ   — 	 ddl m}  |¦   «         pi                      | ¦  «        }t          |t          ¦  «        r$t          |                     d¦  «        d¬¦  «        S n# t          $ r Y nw xY wdS )z�Return True when the user opted into the Tool Gateway for this tool.

    Reads ``<section>.use_gateway`` from config.yaml.  Never raises.
    r   )Úload_configÚuse_gatewayF)Údefault)rW   rm   ÚgetÚ
isinstanceÚdictr   r   )rk   rm   Úsections      r   Úprefers_gatewayrt     s”   € ð
Ø1Ð1Ð1Ð1Ð1Ð1Ø�;‘=”=Ð& B×+Ò+¨NÑ;Ô;ˆÝ�g�tÑ$Ô$ð 	NÝ" 7§;¢;¨}Ñ#=Ô#=ÀuÐMÑMÔMÐMð	Nøåð ð ð Øˆðøøøàˆ5s   ‚AA! Á!
A.Á-A.c                 ó¸   — t          d¦  «        pd} | €%	 ddlm}  |d¦  «        } n# t          $ r d} Y nw xY wt	          | o|                      ¦   «         ¦  «        S )a+  Return True when FAL_KEY is set to a non-whitespace value.

    Consults both ``os.environ`` and ``~/.hermes/.env`` (via
    ``hermes_cli.config.get_env_value`` when available) so tool-side
    checks and CLI setup-time checks agree.  A whitespace-only value
    is treated as unset everywhere.
    ÚFAL_KEYNr   rR   )rL   rW   rS   r   r   r$   )r    rS   s     r   Úfal_key_is_configuredrw   %  s‚   € õ ˜yÑ)Ô)Ð1¨T€EØ€}ð	Ø7Ð7Ð7Ð7Ð7Ð7à!�M )Ñ,Ô,ˆEˆEøÝð 	ð 	ð 	ØˆEˆEˆEð	øøøå�Ð'˜%Ÿ+š+™-œ-Ñ(Ô(Ð(s   •' §6µ6)r   r   r   r   )r   )r   r   r   r   r   r   )r    r!   r   r   )r   r   )
r<   r!   r=   r   r>   r   r;   r?   r   r@   )rG   r   r   r   )rJ   N)rM   r   rN   r   rO   r   r   r   )r   r   )rk   r   r   r   )Ú__doc__Ú
__future__r   Úloggingr7   Úpathlibr   Útypingr   r   Úutilsr   Ú	getLoggerÚ__name__r]   r#   r*   r+   r   r   r'   r-   r/   r:   rF   rL   re   rj   rt   rw   ri   r(   r   ú<module>r€      sÜ  ðØ 0Ð 0à "Ð "Ð "Ð "Ð "Ð "à €€€Ø 	€	€	€	Ø Ð Ð Ð Ð Ð Ø Ð Ð Ð Ð Ð Ð Ð à !Ð !Ð !Ð !Ð !Ð !à	ˆÔ	˜8Ñ	$Ô	$€ð $Ð ØÐ Ø2Ð2Ð2Ð ð 7<ð ð ð ð ð ð ð8 .ðð ðð ð ð ð ð ð61ð 1ð 1ð 1ðð ð ð ð$ð $ð $ð $ð
	ð 	ð 	ð 	ð" $(ð$ð $ð $ð $ð $ð $ðN3ð 3ð 3ð 3ð" Øð	^ð ^ð ^ð ^ð ^ðBð ð ð ð.ð ð ð ð)ð )ð )ð )ð )ð )r(   