
    `gjU6                       U d Z ddlm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 ddlmZ ddlmZmZ  ej"                  e      ZdZd	Zd
ZdZ ej0                         Ze	dd       Zi Zded<   ddZe G d d             Zd dZd!dZ d"dZ!d#dZ"d$dZ#d%dZ$	 d&dd	 	 	 	 	 	 	 	 	 d'dZ%ddd	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 d(dZ&d)dZ'y)*u   OAuth credential storage and refresh for the Honcho memory provider.

An access token authenticates exactly like a scoped API key, so it is stored
as the host's ``apiKey``; this module exchanges the refresh token before
expiry to keep it live.

Refresh tokens rotate with single-use reuse detection: a replayed stale token
revokes the whole grant. So every refresh must persist the rotated token
atomically and be serialized — and a failed refresh never raises into the
agent (stale token stays; the fail-open path absorbs the eventual 401).
    )annotationsN)contextmanager)	dataclass)Path)AnyCallablezhch-at-zhch-rt-x   g      .@c              #    K   t        |  d      }d}	 |j                  j                  dd       t        |d      }t        j
                  dk(  rAddl}|j                  d       |j                  |j                         |j                  d       n.ddl}|j                  |j                         |j                         	 d |	 t        j
                  dk(  rAddl}|j                  d       |j                  |j                         |j$                  d       n.ddl}|j                  |j                         |j&                         |j#                          yy# t        $ r. t        j!                  d	d
       ||j#                          d}Y w xY w# t        $ r Y Ww xY w# |	 t        j
                  dk(  rAddl}|j                  d       |j                  |j                         |j$                  d       n.ddl}|j                  |j                         |j&                         n# t        $ r Y nw xY w|j#                          w w xY ww)u  Machine-wide advisory lock around read-refresh-persist.

    The in-process ``_refresh_lock`` can't stop a second process (a sibling
    Hermes profile or the desktop app sharing this honcho.json) from replaying
    the single-use refresh token and tripping reuse-detection — which revokes
    the whole grant. An OS file lock on ``<config>.lock`` serializes rotation
    across processes; best-effort, so a platform without flock degrades to
    in-process serialization only.
    z.lockNTparentsexist_okza+bntr      z<Honcho OAuth cross-process lock unavailable; in-process only)exc_info)r   parentmkdiropenosnamemsvcrtseeklockingfilenoLK_LOCKfcntlflockLOCK_EX	ExceptionloggerdebugcloseLK_UNLCKLOCK_UN)path	lock_pathfhr   r   s        N/root/.hermes/venv/lib/python3.12/site-packages/plugins/memory/honcho/oauth.py_config_refresh_lockr(   ,   s     vU^$I	Btd;)U#77d?GGAJNN299;:KK		U]]3>77d?!GGAJNN299;C KK		U]]; HHJ   S^bc>HHJB	&   >77d?!GGAJNN299;C KK		U]]; HHJ s   IB+E  F$ IBF 	I4FIFI	F!I F!!I$I(BH+*I+	H74I6H77IIz(dict[tuple[str, str], tuple[float, str]]_expiry_cachec                F    t        |       xr | j                  t              S )zCTrue when ``value`` is an OAuth access token (vs a static API key).)bool
startswithACCESS_TOKEN_PREFIX)values    r'   is_oauth_access_tokenr/   g   s    ;@5++,?@@    c                      e Zd ZU dZded<   ded<   ded<   ded<   ded<   d	Zded
<   dZded<   dZded<   edd       Z	ddZ
edddZy)OAuthCredentialzAn OAuth grant as stored in a honcho.json host block.

    ``access_token`` mirrors the host's ``apiKey``; the remaining fields live in
    the host's ``oauth`` sub-block. ``expires_at`` is absolute epoch seconds.
    straccess_tokenrefresh_tokenfloat
expires_at	client_idtoken_endpointwritescopeBearer
token_typeN
str | Noneconsent_peer_namec                   |j                  d      }|j                  d      }t        |t              rt        |      sy|j                  d      }|j                  d      }|j                  d      }|r|r|sy	 t	        |j                  dd            } | |t        |      |t        |      t        |      t        |j                  d
d            t        |j                  dd                  S # t
        t        f$ r d	}Y ow xY w)zHBuild a credential from a honcho.json host block, or None if incomplete.oauthapiKeyNrefreshTokentokenEndpointclientId	expiresAtr   g        r;   r:   	tokenTyper<   r4   r5   r7   r8   r9   r;   r=   )get
isinstancedictr/   r6   	TypeError
ValueErrorr3   )clsblockrA   accessrefreshendpointr8   r7   s           r'   from_host_blockzOAuthCredential.from_host_block~   s     		'"8$%&.CF.K))N+99_-IIj)	H	uyya89J g,!)nx=eii12599[(;<
 	
 :& 	J	s   :C1 1DDc                    | j                   t        | j                        | j                  | j                  | j
                  | j                  dS )zFThe ``oauth`` sub-block to persist (the access token lives in apiKey).)rC   rF   rE   rD   r;   rG   )r5   intr7   r8   r9   r;   r=   )selfs    r'   oauth_blockzOAuthCredential.oauth_block   sA     !..T__-!00ZZ
 	
r0   )skewc               &    || j                   |z
  k\  S )z@True when the access token is within ``skew`` seconds of expiry.)r7   )rV   nowrX   s      r'   
is_expiredzOAuthCredential.is_expired   s    t-..r0   )rO   dict[str, Any]returnz'OAuthCredential | None')r]   r\   )rZ   r6   rX   r6   r]   r+   )__name__
__module____qualname____doc____annotations__r;   r=   r?   classmethodrS   rW   _REFRESH_SKEW_SECONDSr[    r0   r'   r2   r2   l   sg     NE3J$(z(
 
2	
 7L /r0   r2   c                r    ddl }|j                  | ||      }|j                          |j                         S )zFPOST form-encoded ``data`` to ``url`` and return the parsed JSON body.r   N)datatimeout)httpxpostraise_for_statusjson)urlrg   rh   ri   resps        r'   _http_post_formro      s1    ::cg:6D99;r0   c               2   t        | j                  d| j                  | j                  dt              }|j                  d      }|j                  d      }t        |      r|st        d      	 t        |j                  dd            }t        |t        |      ||z   | j                  | j                  t        |j                  d| j                              t        |j                  d| j                              	      S # t        t        f$ r d}Y w xY w)
zRun the refresh_token grant and return the rotated credential.

    Raises on any transport/protocol failure; callers fail open.
    r5   )
grant_typer8   r5   r4   z3refresh response missing access_token/refresh_token
expires_inr   r;   r=   rH   )ro   r9   r8   r5   _REFRESH_TIMEOUT_SECONDSrI   r/   rM   rU   rL   r2   r3   r;   r=   )credrZ   bodyrP   rQ   rr   s         r'   _exchange_refresh_tokenrv      s    
 )!//	

 	!D XXn%Fhh'G (NOO,23
 'l#..**$((7DJJ/0txxdoo>?  z" 
s   /D DDc                    	 t        j                  | j                  d            S # t        t         j                  f$ r i cY S w xY w)Nutf-8encoding)rl   loads	read_textOSErrorJSONDecodeError)r$   s    r'   _read_configr      s@    zz$..'.:;;T))* 	s   $' AAc                (   | j                   j                  dd       | j                  d| j                   d      }t	        j
                  |d      dz   }t        j                  |t        j                  t        j                  z  t        j                  z  d      }	 t        j                  |d	d
      5 }|j                  |       ddd       t        j                  ||        y# 1 sw Y    xY w# t        $ r |j                  d        w xY w)zFWrite ``raw`` to ``path`` atomically, preserving 0600 on the new file.Tr   .z.tmp   )indent
i  wrx   ry   N)
missing_ok)r   r   	with_namer   rl   dumpsr   r   O_WRONLYO_CREATO_TRUNCfdopenr:   r   unlinkreplace)r$   rawtmptextfdr&   s         r'   _atomic_write_configr      s    KKdT2
..1TYYKt,
-C::c!$t+D	bkkBJJ.;U	CBYYr31 	RHHTN	
 JJsD	 	 

d
#s$   C4 7C(	C4 (C1-C4 4Dc                    |j                         D ]I  \  }}t        |t              r/t        | j                  |      t              rt	        | |   |       E|| |<   K | S )zLRecursively merge ``overlay`` into ``base`` (overlay wins on scalars/lists).)itemsrJ   rK   rI   _deep_merge)baseoverlaykeyr.   s       r'   r   r      sT    mmo 
UeT"z$((3-'FS	5)DI	
 Kr0   c                   t        |       }|j                  di       }|j                  |i       }|j                  |d<   |j                         |d<   t	        | |       |j
                  |j                  ft        t        |       |f<   y)zQPersist ``cred`` into ``host``'s block (apiKey + oauth), leaving all else intact.hostsrB   rA   N)r   
setdefaultr4   rW   r   r7   r)   r3   )r$   hostrt   r   r   rO   s         r'   _persist_credentialr      s{    
t
CNN7B'ET2&E''E(O%%'E'Ns#(,9J9J'KM3t9d#$r0   rZ   c                  |t        j                          n|}t        |       |f}|-t        j                  |      }|||d   t        z
  k  r|d   dfS ||n
t        |       }|j                  d      xs i j                  |      xs i }t        j                  |      }|t        j                  |d       y|j                  |j                  ft        |<   |j                  |      s|j                  dfS t        5  t        |       5  t        |       j                  d      xs i j                  |      xs i }	t        j                  |	      xs |}
|
j                  |      s7|
j                  |
j                  |j                  k7  fcddd       cddd       S 	 t        |
|      }t%        | ||       t         j'                  d	|       |j                  d
fcddd       cddd       S # t        $ rA}t         j#                  d||       |
j                  dfcY d}~cddd       cddd       S d}~ww xY w# 1 sw Y   nxY wddd       y# 1 sw Y   yxY w)a  Return ``(access_token, refreshed)`` for ``host``, refreshing if near expiry.

    Returns ``(None, False)`` when the host has no OAuth credential (e.g. a plain
    API key) so callers leave the existing token untouched. Refresh failures are
    swallowed: the current (possibly stale) token is returned with
    ``refreshed=False`` and the fail-open path handles any resulting 401.
    Nr   r   Fr   )NFr   z+Honcho OAuth refresh failed for host %s: %sz(Honcho OAuth token refreshed for host %sT)timer3   r)   rI   rd   r   r2   rS   popr7   r4   r[   _refresh_lockr(   rv   r   r   warningr   info)r$   r   r   rZ   r   cachedsourcerO   rt   fresh_blockcurrentrotatedexcs                r'   ensure_fresh_tokenr      s2    $))+#Ct9d
C {""3'#q	4I(I"I!9e##OSd);FZZ &B++D17RE**51D|#t$//4+<+<=M#??s?#  %''	 *,T2 * $D)--g6<"AA$GM2!11+>F$!!c!*'')=)=ARAR)RR* * *	/-g3?G 	D$0>E##T)* * *  	/NNH$PST''..* * *	/* * * * *sa   ?IBH=	I G0-0H=	I0	H:9$H5H:H="	I5H::H==I	IIT)apply_configrZ   c               J   |t        j                          n|}|j                  d      }|j                  d      }t        |      r|st        d      	 t	        |j                  dd            }	t        |t        |      ||	z   ||t        |j                  dd            t        |j                  dd	            
      }
t        |       }|j                  d      }t        |t              r$|j                  d      |
_        |rt        ||       |
j                  |
j                  ft        t        |       |f<   |j!                  di       }|j!                  |i       }|
j                  |d<   |
j#                         |d<   t%        | |       |
S # t
        t        f$ r d}	Y 2w xY w)u  Apply a fresh OAuth grant to ``path`` for ``host``.

    Deep-merges the grant's ``config`` (the manifest default_config) into the
    file root — preserving other hosts and root keys — then writes the host's
    ``apiKey`` and ``oauth`` block. ``grant`` is an OAuthTokenResponse dict
    (access_token, refresh_token, expires_in, scope, config).
    ``apply_config=False`` skips the config merge and stores tokens only.
    r4   r5   z(grant missing access_token/refresh_tokenrr   r   r;   r:   r=   r<   rH   configpeerNamer   rB   rA   )r   rI   r/   rM   rU   rL   r2   r3   r   rJ   rK   r?   r   r7   r4   r)   r   rW   r   )r$   r   grantr8   r9   r   rZ   rP   rQ   rr   rt   r   granted_configr   rO   s                  r'   install_grantr   2  s~   $ $))+#CYY~&Fii(G (CDD<34
 'l#%%))GW-.uyyx89D t
CYYx(N.$'!/!3!3J!?^,(,9J9J'KM3t9d#$NN7B'ET2&E''E(O%%'E'Ns#K3 z" 
s   F F"!F"c                J    t        | dd      }|t        |d      sy||_        y)aS  Rotate the live Honcho client's Bearer in place. Returns success.

    The SDK builds its auth header per request from the HTTP client's
    ``api_key``, so mutating it rotates every holder of the singleton without a
    rebuild. Guarded: an SDK shape change degrades to False and the caller can
    fall back to resetting the client.
    _httpNapi_keyFT)getattrhasattrr   )clienttokenhttps      r'   apply_token_to_clientr   g  s-     67D)D|743DLr0   )r$   r   )r.   r>   r]   r+   )rm   r3   rg   zdict[str, str]rh   r6   r]   r\   )rt   r2   rZ   r6   r]   r2   )r$   r   r]   r\   )r$   r   r   r\   r]   None)r   r\   r   r\   r]   r\   )r$   r   r   r3   rt   r2   r]   r   )N)
r$   r   r   r3   r   zdict[str, Any] | NonerZ   float | Noner]   ztuple[str | None, bool])r$   r   r   r3   r   r\   r8   r3   r9   r3   r   r+   rZ   r   r]   r2   )r   r   r   r3   r]   r+   )(ra   
__future__r   rl   loggingr   	threadingr   
contextlibr   dataclassesr   pathlibr   typingr   r   	getLoggerr^   r   r-   REFRESH_TOKEN_PREFIXrd   rs   Lockr   r(   r)   rb   r/   r2   ro   rv   r   r   r   r   r   r   r   re   r0   r'   <module>r      sl  
 #   	   % !   			8	$        
 	  - -n ;=7 <A
 8/ 8/ 8/xBL "&1*
 1*
1*
1* 
1*
 
1* 1*v 2
2
2 2
 2 2 2 
2 2jr0   