
    epj,                        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/thesage/.hermes/hermes-agent/tools/tool_backend_helpers.pymanaged_nous_tools_enabledr      s    HHHHHH 	:77DIIILL7799L% 	511   uu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FH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 )zResolve 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2N*:66O466)#;O(;  )##(7SMS99t	H	$	$'1;88t(7oMo99[eOoxxko ) & 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)zRead 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3s   %( 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mmG,,23399;;CC 	 	 	CCC	
 
 r
333333 %&=&=&=> 	 	H9X&&D|4#7#7#9#9|IIKKE}0"55 5."55  egg	 
  


	  
 
 
:		
 	
 	
 	
 	
 	
 	
 	

 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 )zReturn 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;;gt$$ 	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))1TE}	777777!M),,EE 	 	 	EEE	'%++--(((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(   