Ë
    áß²jS  ã                  óÀ   — d Z ddlmZ ddlZddlmZmZ ddlmZm	Z	m
Z
mZmZ ddlmZ ddlmZ erddlmZmZ  ej(                  e«      Zdd	„Zdd
„Z	 	 	 	 	 	 	 	 dd„Zy)uR  Plugin-provider refresh support for the credential pool (#116408).

A model-provider plugin makes its pooled OAuth rows refreshable by shipping
``ProviderProfile.refresh_credential(entry) -> Mapping | None``. The pool owns
the locking, persistence and failure classification around that hook; the
plugin owns only the token POST. This sibling module keeps that logic out of
``agent/credential_pool.py`` (near the size cap).

Contract (documented in website/docs/developer-guide/model-provider-plugin.md):

* the hook returns a mapping of rotated values â€” dataclass field names
  (``access_token``, ``refresh_token``, ``expires_at_ms`` â€¦) replace the row's
  fields, every other key (``expires_in``, ``token_type``, ``scope`` â€” the
  natural token-endpoint shape) lands in ``entry.extra``; ``None``/empty = could not
  rotate and the pool benches the row like a failed refresh POST;
* raising ``AuthError(..., relogin_required=True)`` (or a grant-dead OAuth code)
  is terminal: the row goes DEAD with a WARNING naming ``hermes auth add``;
  any other exception is transient and only benches the row.
é    )ÚannotationsN)ÚfieldsÚreplace)ÚTYPE_CHECKINGÚAnyÚMappingÚOptionalÚTuple)Ú_OAUTH_GRANT_DEAD_CODES)Ú	AuthError)ÚCredentialPoolÚPooledCredentialc                óš  — |s| S t        |«      }t        t        | «      «      D �ch c]  }|j                  ’Œ c}ddhz
  }|j	                  «       D ��ci c]  \  }}||v sŒ||“Œ }}}|j	                  «       D ��ci c]  \  }}||vsŒ|dk7  sŒ||“Œ }}}|ri | j
                  ¥|¥|d<   |rt        | fi |¤ŽS | S c c}w c c}}w c c}}w )u³  Merge a ``refresh_credential`` return value into *entry*.

    Field names go through ``dataclasses.replace``; everything else is merged into ``extra``
    (mirroring ``PooledCredential.from_dict``). Before this split a token-endpoint-shaped mapping
    with ``expires_in`` raised ``TypeError`` inside ``replace`` and the pool benched a row whose
    single-use refresh token the server had already rotated â€” the login was lost.
    ÚproviderÚextra)Údictr   ÚtypeÚnameÚitemsr   r   )	ÚentryÚresultÚmappingÚfÚfield_namesÚkÚvÚfield_updatesÚextra_updatess	            úA/home/ubuntu/.hermes/hermes-agent/agent/credential_pool_plugin.pyÚapply_plugin_refresh_resultr    $   sÒ   € ñ ØˆÜ!% f£€GÜ#)¬$¨u«+Ó#6Ö7˜a�1—6“6Ò7¸:ÀwÐ:OÑO€KØ&-§m¡m£o×J™d˜a ¸¸kÒ9I�Q˜‘TÐJ€MÑJØ&-§m¡m£o×b™d˜a ¸À+Ò9MÐRSÐWaÓRa�Q˜‘TÐb€MÑbÙØ!A E§K¡KÐ!A°=Ð!Aˆ�gÑÙ.;Œ7�5Ñ*˜MÑ*ÐFÀÐFùò 8ùÛJùÛbs#   ¦B<ÁCÁ!CÁ<CÂ	CÂCc                ó~   — t        | t        «      syt        | j                  «      xs | j                  xs dt
        v S )a«  True when retrying the same plugin refresh token cannot succeed.

    Plugins have no per-provider code table, so the predicate is the structural one every built-in
    flow shares: a structured ``AuthError`` that asks for a re-login, or one carrying a grant-dead
    OAuth code. Transport errors, 429/5xx-shaped ``RuntimeError`` and plain ``AuthError`` without
    that signal stay transient (EXHAUSTED for one cooldown).
    FÚ )Ú
isinstancer   ÚboolÚrelogin_requiredÚcoder   )Úexcs    r   Ú is_terminal_plugin_refresh_errorr(   7   s5   € ô �cœ9Ô%ØÜ�×$Ñ$Ó%ÒT¨#¯(©(ª.°bÔ=TÐ)TÐTó    c                óÖ  — ddl m} | j                  |«      }|j                  |j                  k7  rS|j                  xs dj                  «       r5t        j                  d| j                  «       d | j                  |fi |¤ŽfS t        |«      r[t        j                  d| j                  |j                  xs |j                  dd || j                  «       | j                  ||«       y	y
)us  Recovery for a plugin hook that raised: adopt a peer's rotation, or quarantine a dead grant.

    Returns ``(handled, result)``; ``handled=False`` means the caller should bench the row as a
    transient failure. A peer process may have rotated the single-use token between our in-lock
    sync and the hook's POST â€” adopt that pair before classifying the failure.
    r   )Ú_MARK_OKr"   uB   %s refresh failed but the pool store has newer tokens â€” adoptingTz�%s refresh token for %s is terminally invalid (%s); the credential leaves rotation. Re-run 'hermes auth add %s' to sign in again.Né   )TN)FN)Úagent.credential_poolr+   Ú_sync_entry_from_pool_storeÚrefresh_tokenÚaccess_tokenÚstripÚloggerÚdebugr   Ú_adoptr(   ÚwarningÚlabelÚidÚ_mark_dead_refresh_grant)Úpoolr   r'   r+   Úsynceds        r   Úrecover_failed_plugin_refreshr;   D   sÉ   € õ /à×-Ñ-¨eÓ4€FØ×Ñ˜u×2Ñ2Ò2¸×8KÑ8KÒ8QÈr×7XÑ7XÔ7ZÜ�‰ÐYÐ[_×[hÑ[hÔiØ�[�T—[‘[ Ñ4¨8Ñ4Ð4Ð4Ü'¨Ô,ô 	�‰ð<à�M‰M˜5Ÿ;™;Ò6¨%¯(©(°2°A¨,¸¸T¿]¹]ô	
ð
 	×%Ñ% e¨SÔ1ØØr)   )r   ú'PooledCredential'r   r   Úreturnr<   )r'   ÚBaseExceptionr=   r$   )r9   z'CredentialPool'r   r<   r'   Ú	Exceptionr=   z)Tuple[bool, Optional['PooledCredential']])Ú__doc__Ú
__future__r   ÚloggingÚdataclassesr   r   Útypingr   r   r   r	   r
   Úhermes_cli.authr   Úhermes_cli.auth_constantsr   r-   r   r   Ú	getLoggerÚ__name__r2   r    r(   r;   © r)   r   ú<module>rJ      sj   ðñõ( #ã ß 'ß ?Õ ?å 3Ý /áßFà	ˆ×	Ñ	˜8Ó	$€óGó&
UðØ
ðØ#5ðØ<Eðà.ôr)   