
    epjD                       d Z ddlmZ ddlZddlZddlZddlmZmZ ddlm	Z	 ddl
mZmZ ddlmZ  ej        e          ZddlmZ dd	lmZ d
ZdZdZ e	d           G d d                      Zd Zd=dZd>dZd?dZd@dZd@d Zd@d!Z dAd#Z!dBd%Z"	 	 dCdDd+Z#	 	 dCdEd,Z$d-Z%dBd.Z&dBd/Z'	 dFdGd0Z(	 dFdHd2Z)	 	 dCdId4Z*d5Z+d6Z,d7Z-dAd8Z.	 	 dCdJd<Z/dS )KzIGeneric managed-tool gateway helpers for Nous-hosted vendor passthroughs.    )annotationsN)datetimetimezone)	dataclass)CallableOptional)urlsplitget_hermes_home)managed_nous_tools_enabledznousresearch.comhttpsx   T)frozenc                  8    e Zd ZU ded<   ded<   ded<   ded<   dS )ManagedToolGatewayConfigstrvendorgateway_originnous_user_tokenboolmanaged_modeN)__name__
__module____qualname____annotations__     @/home/thesage/.hermes/hermes-agent/tools/managed_tool_gateway.pyr   r      s?         KKKr   r   c                 $    t                      dz  S )zDReturn the Hermes auth store path, respecting HERMES_HOME overrides.z	auth.jsonr
   r   r   r   auth_json_pathr       s    {**r   returnOptional[dict]c                 v   	 t                      } |                                 sd S t          j        |                     d                    }|                    di           }t          |t                    sd S |                    di           }t          |t                    r|S n# t          $ r Y nw xY wd S )Nzutf-8)encoding	providersnous)	r    is_filejsonloads	read_textget
isinstancedict	Exception)pathdatar%   nous_providers       r   _read_nous_provider_stater2   $   s    ||~~ 	4z$..'.::;;HH["--	)T** 	4!fb11mT** 	!  	!   4s   "B) AB) ;,B) )
B65B6valueobjectOptional[datetime]c                   t          | t                    r|                                 sd S |                                 }|                    d          r|d d         dz   }	 t	          j        |          }n# t          $ r Y d S w xY w|j         |                    t          j
                  }|                    t          j
                  S )NZz+00:00)tzinfo)r,   r   stripendswithr   fromisoformat
ValueErrorr9   replacer   utc
astimezone)r3   
normalizedparseds      r   _parse_timestamprC   5   s    eS!!  tJ3 0_x/
'
33   tt}x|44X\***s   #A8 8
BB
expires_atskew_secondsintr   c                    t          |           }|dS |t          j        t          j                  z
                                  }|t          dt          |                    k    S )NTr   )rC   r   nowr   r?   total_secondsmaxrF   )rD   rE   expires	remainings       r   _access_token_is_expiringrM   D   sX    z**Gt8<555DDFFIAs<001111r   Optional[str]c                 4   	 ddl m} m} 	  |d          }n# | $ r t          j        d          }Y nw xY wn$# t
          $ r t          j        d          }Y nw xY wt          |t                    r(|                                r|                                S dS )as  Read the TOOL_GATEWAY_USER_TOKEN env override through the secret scope.

    Availability scans run both inside agent turns (scope installed) and in
    unscoped CLI paths, so this uses the Slack pattern: honor the scope's
    verdict when installed (a scoped miss does NOT borrow the process env
    under multiplex), fall back to ``os.environ`` only when unscoped.
    r   )UnscopedSecretError
get_secretTOOL_GATEWAY_USER_TOKENN)	agent.secret_scoperP   rQ   osgetenvr.   r,   r   r:   )rP   rQ   explicits      r   _read_user_token_overriderW   L   s    8FFFFFFFF	<!z";<<HH" 	< 	< 	<y!:;;HHH	< 8 8 896778(C    X^^%5%5  ~~4s'   7  7 37 37 AAc                     t                      } | r| S t                      pi }|                    d          }t          |t                    r(|                                r|                                S dS )a  Cheap probe for a Nous gateway token without triggering refresh.

    Availability scans (`hermes tools`, banner/status paint, provider
    `is_available()` checks) must stay off the synchronous OAuth refresh path.
    This helper therefore only inspects the explicit env override and the
    cached auth-store token, without checking expiry and without making any
    network calls. Truthful refresh handling stays in request/session paths
    that call :func:`read_nous_access_token`.
    access_tokenN)rW   r2   r+   r,   r   r:   )rV   r1   rY   s      r   peek_nous_access_tokenrZ   b   s{     )**H -//52M $$^44L,$$ $););)=)= $!!###4r   c                    t                      } | r| S t                      pi }t                      }|r*t          |                    d          t
                    s|S 	 ddlm}  |t
                    }t          |t                    r(|
                                r|
                                S n2# t          $ r%}t                              d|           Y d}~nd}~ww xY w|S )zJRead a Nous Subscriber OAuth access token from auth store or env override.rD   r   )resolve_nous_access_token)refresh_skew_secondsz$Nous access token refresh failed: %sN)rW   r2   rZ   rM   r+   '_NOUS_ACCESS_TOKEN_REFRESH_SKEW_SECONDShermes_cli.authr\   r,   r   r:   r.   loggerdebug)rV   r1   cached_tokenr\   refreshed_tokenexcs         r   read_nous_access_tokenre   w   s&   (**H -//52M)++L 5,''/   	B======33!H
 
 
 os++ 	+0E0E0G0G 	+"((*** B B B;SAAAAAAAAB s   AB3 3
C"=CC"r   c                     t          j        dd                                                                          } | st          S | dv r| S t          d          )z,Return configured shared gateway URL scheme.TOOL_GATEWAY_SCHEME >   httpr   z-TOOL_GATEWAY_SCHEME must be 'http' or 'https')rT   rU   r:   lower_DEFAULT_TOOL_GATEWAY_SCHEMEr=   )schemes    r   get_tool_gateway_schemerm      sZ    Y,b117799??AAF ,++"""
D
E
EEr   r   c                   |                                                      dd           d}t          j        |d                                                              d          }|r|S t                      }t          j        dd                                                              d          }|r
| d|  d| S | d|  dt           S )	z0Return the gateway origin for a specific vendor.-__GATEWAY_URLrh   /TOOL_GATEWAY_DOMAIN://z	-gateway.)upperr>   rT   rU   r:   rstriprm   _DEFAULT_TOOL_GATEWAY_DOMAIN)r   
vendor_keyexplicit_vendor_urlshared_schemeshared_domains        r   build_vendor_gateway_urlr|      s    LLNN**344BBBJ)J3399;;BB3GG #""+--MI3R88>>@@FFsKKM EDDFDD]DDDOOOO1MOOOr   gateway_builderOptional[Callable[[str], str]]token_reader%Optional[Callable[[], Optional[str]]]"Optional[ManagedToolGatewayConfig]c                    t                      sdS |pt          }|pt          } ||           } |            }|r|sdS t          | ||d          S )z8Resolve shared managed-tool gateway config for a vendor.NT)r   r   r   r   )r   r|   re   r   )r   r}   r   resolved_gateway_builderresolved_token_readerr   r   s          r   resolve_managed_tool_gatewayr      s     &'' t.J2J(B,B--f55N++--O  t#%'	   r   c                8    t          | ||pt                    duS )a|  Return True when gateway URL and a likely-usable Nous token are present.

    Defaults to :func:`peek_nous_access_token` so read-only availability scans
    avoid synchronous OAuth refresh. Callers that are about to make a real
    gateway request should use :func:`resolve_managed_tool_gateway` (which
    still defaults to the refresh-aware :func:`read_nous_access_token`).
    )r}   r   N)r   rZ   )r   r}   r   s      r   is_managed_tool_gateway_readyr      s4     ('!;%;   	 r   toolc                    d|  S )zABase path for a managed vendor's REST routes on the gateway host.z/api/r   r   s    r   managed_vendor_base_pathr      s    6r   c                    d|  S )z=Media upload endpoint for a managed vendor, on the same host.z/api/uploads/r   r   s    r   managed_vendor_upload_pathr      s    #6###r   c                    |pt           }	  |t                                        d          }n# t          $ r Y dS w xY w|sdS || t	          |            t          |           dS )uD  Absolute URLs for a managed vendor, or ``None`` when none resolves.

    Address resolution only: entitlement is deliberately not consulted here.
    What an account may spend on a managed vendor is the gateway's own
    decision, stated in its refusals, and re-deciding it on the client can only
    ever disagree with the server. A caller that wants to hide its tools from
    users who could not call them at all does that in its ``check_fn``.

    ``None`` means no origin could be resolved — a misconfigured
    ``TOOL_GATEWAY_SCHEME`` — so there is nothing to call.
    rr   N)originbase_urlupload_path)r|   _MANAGED_GATEWAY_VENDORrv   r=   r   r   )r   r}   builderr   s       r   managed_vendor_endpointsr      s     9!9G01188==   tt t A7??AA1&99  s   #/ 
==urlc                p   t          | t                    r|                                 sdS |pt          }	 t	           |t
                              }t	          |                                           }n# t          $ r Y dS w xY wt          |j                  o|j        |j	        f|j        |j	        fk    S )u  True when ``url`` is on the Nous tool-gateway origin this client builds.

    Anything granting a URL extra trust — our bearer, reading files off disk to
    upload — must gate on this rather than on a name, so an arbitrary URL can
    never inherit that trust.
    F)
r,   r   r:   r|   r	   r   r=   r   rl   netloc)r   r}   r   expectedactuals        r   is_managed_nous_gateway_urlr     s     c3 syy{{ u9!9GGG$;<<==#))++&&   uu gFM6=#AhoW_WfEg#ggs   >A5 5
BBr-   c                B   t          | |          si S |pt          }	  |            }n5# t          $ r(}t                              d| |           i cY d}~S d}~ww xY wt          |t                    r|                                si S dd|                                 iS )a  Live auth headers for a managed gateway URL, or ``{}`` when not managed.

    Read fresh on every call rather than cached: a Nous access token expires
    within the hour, and a long session would otherwise keep presenting a dead
    bearer. Returns ``{}`` rather than raising when no token is available, so a
    caller can report "sign in" instead of sending an unauthenticated request.
    z,Managed gateway token read failed for %s: %sNAuthorizationzBearer )r   re   r.   r`   ra   r,   r   r:   )r   r}   r   r   tokenrd   s         r   managed_gateway_auth_headersr   -  s     'sO<< 	(B,B%%''   CS#NNN						 eS!!  	6u{{}}6677s   
( 
AAAAg      .@g      N@g     r@c                0   	 |                                  }|                    di                               d          }t          |t                    r(|                                r|                                S n# t
          $ r Y nw xY wd| j         dS )a
  A model-actionable reason from a gateway refusal, or a generic one.

    The gateway's 4xx bodies carry deliberate guidance (rate-limit waits, size
    caps, "you could not submit anyway"), so surface `error.message` verbatim
    rather than a bare status code.
    errormessagez%the gateway refused the upload (HTTP ))r(   r+   r,   r   r:   r.   status_code)responsepayloadr   s      r   _describe_media_upload_refusalr   ]  s    --//++gr**..y99gs## 	# 	#==??"   J83GJJJJs   A9A= =
B
	B

server_urlr   Optional[Callable]c                &    t                     sdS t          |t                    r|                    d          sdS t	          t                                                               }|j         d|j         }| | d
 fd	}|S )u  Async ``(data, mime) -> argument value`` uploader for one managed vendor.

    Returns ``None`` when there is no usable upload endpoint (not a managed
    Nous URL, or no ``upload_path``); callers then refuse local paths with a
    clear message instead of silently forwarding them.

    The three steps of the protocol:

    1. POST ``origin + upload_path`` with the declared content type and exact
       byte length, using the same live auth headers as the vendor calls.
       The gateway answers with a presigned single-object PUT URL (short
       expiry; type and length are signed into it) and an upload token.
    2. PUT the bytes to that URL. This goes directly to storage — never
       through the gateway — which is what removes the request-size ceiling.
    3. Return ``nous-upload:<token>`` for the tool argument. The token is
       bound to this Nous principal and is redeemable only through the
       gateway, so it is inert anywhere else it might end up.
    Nrr   rt   r0   bytesmimer   r!   c           	       K   dd l }ddlm} t                    }|st	          d          |                    t                    }|                    |          4 d {V }|                    ||t          |           d           d {V }d d d           d {V  n# 1 d {V swxY w Y   |j
        dk    rt	          t          |                    	 |                                }n# t          $ r d }Y nw xY wt          |t                    r|                    d          nd }	t          |t                    r|                    d	          nd }
t          |	t"                    r|	rt          |
t"                    r|
st	          d
          |                    t          t$          t&                    } ||          4 d {V }|                    |	| d|i           d {V }d d d           d {V  n# 1 d {V swxY w Y   |j
        dk    rt	          d|j
         d          d|
 S )Nr   )create_ssrf_safe_async_clientz.no Nous credential is available for the upload)timeout)contentTypecontentLength)headersr(      	uploadUrlr   z+the gateway's upload response was malformed)readwritezContent-Type)contentr   z!storage refused the upload (HTTP r   znous-upload:)httpxtools.url_safetyr   r   RuntimeErrorTimeout%_MEDIA_UPLOAD_PRESIGN_TIMEOUT_SECONDSAsyncClientpostlenr   r   r(   r.   r,   r-   r+   r   &_MEDIA_UPLOAD_PUT_READ_TIMEOUT_SECONDS'_MEDIA_UPLOAD_PUT_WRITE_TIMEOUT_SECONDSput)r0   r   r   r   r   presign_timeoutclientpresignr   
upload_urlr   put_timeoutr   r}   presign_urlr   r   s                r   uploadz,build_managed_media_uploader.<locals>.upload  s     BBBBBB.z?LYY 	QOPPP  --(MNN$$_$== 	 	 	 	 	 	 	"KK%)CIIFF (        G	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 #%%=gFFGGG	llnnGG 	 	 	GGG	1;GT1J1JTW[[---PT
(27D(A(AKG$$$t:s++ 	N
 	Nz%QT?U?U 	NZ_ 	NLMMMmm179 $ 
 

 10EEE 	] 	] 	] 	] 	] 	] 	] 

:tnVZE[
\\\\\\\\C	] 	] 	] 	] 	] 	] 	] 	] 	] 	] 	] 	] 	] 	] 	] 	] 	] 	] 	] 	] 	] 	] 	] 	] 	] 	] 	] ?c!!U3?UUUVVV%e%%%s6   &/B''
B14B1 C5 5DD!H
HH)r0   r   r   r   r!   r   )r   r,   r   
startswithr	   r:   rl   r   )r   r   r}   r   partsr   r   r   s   ` ``   @r   build_managed_media_uploaderr   n  s    0 'z?CC tk3'' {/E/Ec/J/J tS__**,,--E////F*[**K2& 2& 2& 2& 2& 2& 2& 2& 2&h Mr   )r!   r"   )r3   r4   r!   r5   )rD   r4   rE   rF   r!   r   )r!   rN   )r!   r   )r   r   r!   r   )NN)r   r   r}   r~   r   r   r!   r   )r   r   r}   r~   r   r   r!   r   )N)r   r   r}   r~   r!   r"   )r   r4   r}   r~   r!   r   )r   r4   r}   r~   r   r   r!   r-   )
r   r4   r   r4   r}   r~   r   r   r!   r   )0__doc__
__future__r   r(   loggingrT   r   r   dataclassesr   typingr   r   urllib.parser	   	getLoggerr   r`   hermes_constantsr   tools.tool_backend_helpersr   rw   rk   r^   r   r    r2   rC   rM   rW   rZ   re   rm   r|   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   <module>r      s   O O " " " " " "   				 ' ' ' ' ' ' ' ' ! ! ! ! ! ! % % % % % % % % ! ! ! ! ! !		8	$	$ , , , , , , A A A A A A1 & *- ' $       + + +
   "+ + + +2 2 2 2   ,   *   8	F 	F 	F 	FP P P P" 7;:>    6 7;:>    J !    
$ $ $ $ 7;    @ 7;h h h h h2 7;:>8 8 8 8 8R )- % *. &*/ 'K K K K( 7;:>	U U U U U U Ur   