§
    ‰eîiX¶ ã                   óD  — d Z ddlZddlZddlZddlZddlZddl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mZ ddlmZ ddlmZ  ej        e¦  «        Zdedefd„Zded	edefd
„Zdededefd„Zdedefd„Zdedz  fd„Zdedeeedz  f         fd„Z de!e         fd„Z"dpdedededz  defd„Z#dee!e         z  eedf         z  e$e         z  dz  defd„Z%	 dpddœdedz  dee!e         z  eedf         z  e$e         z  dz  dedz  fd„Z&dedz  de'fd„Z(dedz  dee'e'f         fd„Z)ddl*m+Z+m,Z, dd l-m-Z- dd!l.m/Z/ dd"l0m1Z1m2Z2m3Z3m4Z4m5Z5m6Z6m7Z7 dd#l8m9Z9 dd!l.m/Z: ej;         <                    d e e:e=¦  «         >                    ¦   «         j?        d$         ¦  «        ¦  «         dd%l@mAZAmBZB dd&lCmDZDmEZE dd'lFmGZG d(ZHdqd*ed+edefd,„ZId-„ ZJ eGd.d/¦  «        ZKde/fd0„ZLd1eMdefd2„ZNdrd1eMd4edefd5„ZOdsd*ed4ed6edefd7„ZPdtd9edefd:„ZQ eGd;d<¦  «        ZRde/fd=„ZSdud1eMd4edefd?„ZTdvd*ed4ed6edefd@„ZU eGdAdB¦  «        ZVdCdDdEdFdGdHœZWde/fdI„ZXdwd1eMd4edefdK„ZY eGdLdM¦  «        ZZdNdOdPdPdQdRdSdTdUœZ[de/fdV„Z\d1eMdWedefdX„Z]dtd9edefdY„Z^ G dZ„ d[e9¦  «        Z_ G d\„ d]e9¦  «        Z`e+ G d^„ d_¦  «        ¦   «         Zae+ G d`„ da¦  «        ¦   «         Zbdbdcœdde1eeaf         deedfeadgeddf
dh„ZcdiZde5eage6e3e                  f         Ze	 dpdje'dkedledz  dedz  fdm„Zf G dn„ doe¦  «        ZgdS )xz�
Base platform adapter interface.

All platform adapters (Telegram, Discord, WhatsApp) inherit from this
and implement the required methods.
é    N)ÚABCÚabstractmethod)Úurlsplit)Únormalize_proxy_urlÚsÚreturnc                 óL   — t          |                      d¦  «        ¦  «        dz  S )u  Count UTF-16 code units in *s*.

    Telegram's message-length limit (4 096) is measured in UTF-16 code units,
    **not** Unicode code-points.  Characters outside the Basic Multilingual
    Plane (emoji like ðŸ˜€, CJK Extension B, musical symbols, â€¦) are encoded as
    surrogate pairs and therefore consume **two** UTF-16 code units each, even
    though Python's ``len()`` counts them as one.

    Ported from nearai/ironclaw#2304 which discovered the same discrepancy in
    Rust's ``chars().count()``.
    z	utf-16-leé   )ÚlenÚencode)r   s    ú8/home/div/.hermes/hermes-agent/gateway/platforms/base.pyÚ	utf16_lenr      s#   € õ ˆq�xŠx˜Ñ$Ô$Ñ%Ô%¨Ñ*Ð*ó    Úlimitc                 óÖ   — t          | ¦  «        |k    r| S dt          | ¦  «        }}||k     r4||z   dz   dz  }t          | d|…         ¦  «        |k    r|}n|dz
  }||k     °4| d|…         S )uÌ   Return the longest prefix of *s* whose UTF-16 length â‰¤ *limit*.

    Unlike a plain ``s[:limit]``, this respects surrogate-pair boundaries so
    we never slice a multi-code-unit character in half.
    r   é   r
   N)r   r   )r   r   ÚloÚhiÚmids        r   Ú_prefix_within_utf16_limitr   *   s‰   € õ ��|„|�uÒÐØˆà•�A‘”ˆ€BØ
ˆrŠ'ˆ'Ø�B‰w˜‰{˜qÑ ˆÝ�Q�t˜�t”WÑÔ Ò&Ð&ØˆBˆBà�q‘ˆBð ˆrŠ'ˆ'ð ˆSˆbˆSŒ6€Mr   Úbudgetc                 óÐ   —  || ¦  «        |k    rt          | ¦  «        S dt          | ¦  «        }}||k     r0||z   dz   dz  } || d|…         ¦  «        |k    r|}n|dz
  }||k     °0|S )a8  Return the largest codepoint offset *n* such that ``len_fn(s[:n]) <= budget``.

    Used by :meth:`BasePlatformAdapter.truncate_message` when *len_fn* measures
    length in units different from Python codepoints (e.g. UTF-16 code units).
    Falls back to binary search which is O(log n) calls to *len_fn*.
    r   r   r
   N©r   )r   r   Úlen_fnr   r   r   s         r   Ú_custom_unit_to_cpr   =   s‰   € ð €vˆa�y„y�FÒÐÝ�1‰vŒvˆØ•�A‘”ˆ€BØ
ˆrŠ'ˆ'Ø�B‰w˜‰{˜qÑ ˆØˆ6�!�D�S�D”'‰?Œ?˜fÒ$Ð$ØˆBˆBà�q‘ˆBð ˆrŠ'ˆ'ð €Ir   Úhostc                 ó”  — 	 t          j        | ¦  «        }|j        rdS t          |dd¦  «        r|j        j        rdS dS # t
          $ r Y nw xY w	 t          j        | dt          j        t          j	        ¦  «        }|D ],\  }}}}}t          j        |d         ¦  «        }|j        s dS Œ-dS # t          j
        t          f$ r Y dS w xY w)a  Return True if *host* would expose the server beyond loopback.

    Loopback addresses (127.0.0.1, ::1, IPv4-mapped ::ffff:127.0.0.1)
    are local-only.  Unspecified addresses (0.0.0.0, ::) bind all
    interfaces.  Hostnames are resolved; DNS failure fails closed.
    FÚipv4_mappedNTr   )Ú	ipaddressÚ
ip_addressÚis_loopbackÚgetattrr   Ú
ValueErrorÚ_socketÚgetaddrinfoÚ	AF_UNSPECÚSOCK_STREAMÚgaierrorÚOSError)r   ÚaddrÚresolvedÚ_familyÚ_typeÚ_protoÚ
_canonnameÚsockaddrs           r   Úis_network_accessibler1   P   s  € ðÝÔ# DÑ)Ô)ˆØÔð 	Ø�5õ �4˜¨Ñ-Ô-ð 	°$Ô2BÔ2Nð 	Ø�5ØˆtøÝð ð ð àˆðøøøðÝÔ&Ø�$�Ô)­7Ô+>ñ
ô 
ˆð
 =Eð 	ð 	Ñ8ˆG�U˜F J°ÝÔ'¨°¬Ñ4Ô4ˆDØÔ#ð Ø�t�tðàˆuøÝÔ�gÐ&ð ð ð Øˆtˆtðøøøs/   ‚A  ŸA  Á 
AÁAÁAB- Â*B- Â-CÃCc                  ó2  — t           j        dk    rdS 	 t          j        ddgddt          j        ¬¦  «        } n# t
          $ r Y dS w xY wi }|                      ¦   «         D ]\}|                     ¦   «         }d|v rB|                     d¦  «        \  }}}|                     ¦   «         ||                     ¦   «         <   Œ]d	D ]W\  }}}| 	                    |¦  «        d
k    r8| 	                    |¦  «        }	| 	                    |¦  «        }
|	r|
r
d|	› d|
› �c S ŒXdS )zîRead the macOS system HTTP(S) proxy via ``scutil --proxy``.

    Returns an ``http://host:port`` URL string if an HTTP or HTTPS proxy is
    enabled, otherwise *None*.  Falls back silently on non-macOS or on any
    subprocess error.
    ÚdarwinNÚscutilz--proxyé   T)ÚtimeoutÚtextÚstderrz : ))ÚHTTPSEnableÚ
HTTPSProxyÚ	HTTPSPort)Ú
HTTPEnableÚ	HTTPProxyÚHTTPPortÚ1zhttp://ú:)
ÚsysÚplatformÚ
subprocessÚcheck_outputÚDEVNULLÚ	ExceptionÚ
splitlinesÚstripÚ	partitionÚget)ÚoutÚpropsÚlineÚkeyÚ_ÚvalÚ
enable_keyÚhost_keyÚport_keyr   Úports              r   Ú_detect_macos_system_proxyrU   s   sU  € õ „|�xÒÐØˆtðÝÔ%Ø�yÐ!¨1°4Å
Ô@Rð
ñ 
ô 
ˆˆøõ ð ð ð Øˆtˆtðøøøð €EØ—’Ñ Ô ð -ð -ˆØ�zŠz‰|Œ|ˆØ�Dˆ=ˆ=ØŸ.š.¨Ñ/Ô/‰KˆC��CØ!$§¢¡¤ˆE�#—)’)‘+”+Ñøð+ð /ð /Ñ&ˆ
�H˜hð �9Š9�ZÑ Ô  CÒ'Ð'Ø—9’9˜XÑ&Ô&ˆDØ—9’9˜XÑ&Ô&ˆDØð /˜ð /Ø. Ð.Ð.¨Ð.Ð.Ð.Ð.Ð.øØˆ4s   ”$9 ¹
AÁAÚvaluec                 óª  — t          | pd¦  «                             ¦   «         }|sdS d|v rDt          |¦  «        }|j        pd                     ¦   «                              d¦  «        |j        fS |                     d¦  «        r˜d|v r”|dd …                              d¦  «        \  }}}d }|                     d¦  «        r3|dd …          	                    ¦   «         rt          |dd …         ¦  «        }|                     ¦   «                              d¦  «        |fS |                     d¦  «        dk    rc|                     d¦  «        \  }}}| 	                    ¦   «         r6|                     ¦   «                              d¦  «        t          |¦  «        fS |                     ¦   «                              d	¦  «                             d¦  «        d fS )
NÚ )rX   Nú://ú.Ú[Ú]r   r@   z[])ÚstrrH   r   ÚhostnameÚlowerÚrstriprT   Ú
startswithrI   ÚisdigitÚintÚcountÚ
rpartition)rV   ÚrawÚparsedr   rO   ÚrestrT   Ú
maybe_ports           r   Ú_split_host_portrj   —   sª  € Ý
ˆeˆk�rÑ
Ô
×
 Ò
 Ñ
"Ô
"€CØð ØˆxØ�€|€|Ý˜#‘”ˆØ”Ð% 2×,Ò,Ñ.Ô.×5Ò5°cÑ:Ô:¸F¼KÐGÐGØ
‡~‚~�cÑÔð .˜s c˜z˜zØ˜A˜B˜Bœ×)Ò)¨#Ñ.Ô.‰ˆˆa�ØˆØ�?Š?˜3ÑÔð 	! D¨¨¨¤H×$4Ò$4Ñ$6Ô$6ð 	!Ý�t˜A˜B˜B”x‘=”=ˆDØ�zŠz‰|Œ|×"Ò" 3Ñ'Ô'¨Ð-Ð-Ø
‡y‚y��~„~˜ÒÐØ!Ÿnšn¨SÑ1Ô1Ñˆˆa�Ø×ÒÑÔð 	=Ø—:’:‘<”<×&Ò& sÑ+Ô+­S°©_¬_Ð<Ð<Ø�9Š9‰;Œ;×Ò˜TÑ"Ô"×)Ò)¨#Ñ.Ô.°Ð4Ð4r   c                  ó¸   — g } dD ]T}t           j                             |d¦  «        }|                      d„ |                     d¦  «        D ¦   «         ¦  «         ŒU| S )N)ÚNO_PROXYÚno_proxyrX   c              3   óf   K  — | ],}|                      ¦   «         ¯|                      ¦   «         V — Œ-d S ©N©rH   )Ú.0Úparts     r   ú	<genexpr>z$_no_proxy_entries.<locals>.<genexpr>¯   s7   è è € ÐOÐO¨À$Ç*Â*Á,Ä,ÐO�t—z’z‘|”|ÐOÐOÐOÐOÐOÐOr   Ú,)ÚosÚenvironrJ   ÚextendÚsplit)ÚentriesrN   rf   s      r   Ú_no_proxy_entriesrz   «   s`   € Ø€GØ'ð Pð PˆÝŒj�nŠn˜S "Ñ%Ô%ˆØ�ŠÐOÐO°·	²	¸#±´ÐOÑOÔOÑOÔOÐOÐOØ€Nr   ÚentryrT   c                 ó$  — t          | pd¦  «                             ¦   «                              ¦   «         }|sdS |dk    rdS t          |¦  «        \  }}|�
|�||k    rdS |�|€dS |sdS 	 t	          j        |d¬¦  «        }	 t	          j        |¦  «        |v S # t          $ r Y dS w xY w# t          $ r Y nw xY w	 t	          j        |¦  «        }	 t	          j        |¦  «        |k    S # t          $ r Y dS w xY w# t          $ r Y nw xY w|                     d¦  «        r|dd …         }| 	                    |¦  «        S |                     d¦  «        r#||dd …         k    p| 	                    |¦  «        S ||k    p| 	                    d|› �¦  «        S )	NrX   FÚ*T)Ústrictz*.r   rZ   )
r]   rH   r_   rj   r   Ú
ip_networkr    r#   ra   Úendswith)	r{   r   rT   ÚtokenÚ
token_hostÚ
token_portÚnetworkÚtoken_ipÚsuffixs	            r   Ú_no_proxy_entry_matchesr‡   ³   s  € Ý��˜ÑÔ×"Ò"Ñ$Ô$×*Ò*Ñ,Ô,€EØð ØˆuØ�‚|€|Øˆtå-¨eÑ4Ô4Ñ€J�
ØÐ $Ð"2°zÀTÒ7IÐ7IØˆuØÐ $ ,ØˆuØð ØˆuðÝÔ& z¸%Ð@Ñ@Ô@ˆð	ÝÔ'¨Ñ-Ô-°Ð8Ð8øÝð 	ð 	ð 	Ø�5�5ð	øøøøåð ð ð ØˆðøøøðÝÔ'¨
Ñ3Ô3ˆð	ÝÔ'¨Ñ-Ô-°Ò9Ð9øÝð 	ð 	ð 	Ø�5�5ð	øøøøåð ð ð Øˆðøøøð ×Ò˜TÑ"Ô"ð %Ø˜A˜B˜B”ˆØ�}Š}˜VÑ$Ô$Ð$Ø×Ò˜SÑ!Ô!ð CØ�z ! " "”~Ò%ÐB¨¯ª°zÑ)BÔ)BÐBØ�:ÒÐ@ §¢Ð/?°:Ð/?Ð/?Ñ!@Ô!@Ð@s`   Á+B) ÂB Â
B&Â"B) Â%B&Â&B) Â)
B6Â5B6Â:C8 ÃC' Ã'
C5Ã1C8 Ã4C5Ã5C8 Ã8
DÄDÚtarget_hosts.c                 ó  ‡‡— t          ¦   «         }|r| sdS t          | t          ¦  «        r| g}nt          | ¦  «        }|D ]C}t	          t          |¦  «        ¦  «        \  ŠŠ‰sŒ$t          ˆˆfd„|D ¦   «         ¦  «        r dS ŒDdS )zÍReturn True when NO_PROXY/no_proxy matches at least one target host.

    Supports exact hosts, domain suffixes, wildcard suffixes, IP literals,
    CIDR ranges, optional host:port entries, and ``*``.
    Fc              3   ó:   •K  — | ]}t          |‰‰¦  «        V — Œd S ro   )r‡   )rq   r{   r   rT   s     €€r   rs   z&should_bypass_proxy.<locals>.<genexpr>í   s0   øè è € ÐOÐO¸eÕ& u¨d°DÑ9Ô9ÐOÐOÐOÐOÐOÐOr   T)rz   Ú
isinstancer]   Úlistrj   Úany)rˆ   ry   Ú
candidatesÚ	candidater   rT   s       @@r   Úshould_bypass_proxyr�   Ü   s·   øø€ õ  Ñ!Ô!€GØð ˜,ð ØˆuÝ�,¥Ñ$Ô$ð (Ø"�^ˆ
ˆ
å˜,Ñ'Ô'ˆ
Øð ð ˆ	Ý%¥c¨)¡n¤nÑ5Ô5‰
ˆˆdØð 	ØÝÐOÐOÐOÐOÐOÀwÐOÑOÔOÑOÔOð 	Ø�4�4ð	àˆ5r   )rˆ   Úplatform_env_varc                óÊ  — | rUt           j                             | ¦  «        pd                     ¦   «         }|r t	          |¦  «        rdS t          |¦  «        S dD ]Z}t           j                             |¦  «        pd                     ¦   «         }|r#t	          |¦  «        r dS t          |¦  «        c S Œ[t          t          ¦   «         ¦  «        }|rt	          |¦  «        rdS |S )uˆ  Return a proxy URL from env vars, or macOS system proxy.

    Check order:
      0. *platform_env_var* (e.g. ``DISCORD_PROXY``) â€” highest priority
      1. HTTPS_PROXY / HTTP_PROXY / ALL_PROXY (and lowercase variants)
      2. macOS system proxy via ``scutil --proxy`` (auto-detect)

    Returns *None* if no proxy is found, or if NO_PROXY/no_proxy matches one
    of ``target_hosts``.
    rX   N)ÚHTTPS_PROXYÚ
HTTP_PROXYÚ	ALL_PROXYÚhttps_proxyÚ
http_proxyÚ	all_proxy)ru   rv   rJ   rH   r�   r   rU   )r‘   rˆ   rV   rN   Údetecteds        r   Úresolve_proxy_urlrš   ò   s  € ð ð .Ý”—’Ð 0Ñ1Ô1Ð7°R×>Ò>Ñ@Ô@ˆØð 	.Ý" <Ñ0Ô0ð Ø�tÝ& uÑ-Ô-Ð-ð:ð .ð .ˆå”—’ Ñ$Ô$Ð*¨×1Ò1Ñ3Ô3ˆØð 	.Ý" <Ñ0Ô0ð Ø�t�tÝ& uÑ-Ô-Ð-Ð-Ð-ð	.õ #Õ#=Ñ#?Ô#?Ñ@Ô@€HØð Õ'¨Ñ5Ô5ð ØˆtØ€Or   Ú	proxy_urlc                 óþ   — | si S |                       ¦   «                              d¦  «        rO	 ddlm} |                     | d¬¦  «        }d|iS # t
          $ r  t                               d| ¦  «         i cY S w xY wd| iS )	u¯  Build kwargs for ``commands.Bot()`` / ``discord.Client()`` with proxy.

    Returns:
      - SOCKS URL  â†’ ``{"connector": ProxyConnector(..., rdns=True)}``
      - HTTP URL   â†’ ``{"proxy": url}``
      - *None*     â†’ ``{}``

    ``rdns=True`` forces remote DNS resolution through the proxy â€” required
    by many SOCKS implementations (Shadowrocket, Clash) and essential for
    bypassing DNS pollution behind the GFW.
    Úsocksr   ©ÚProxyConnectorT©ÚrdnsÚ	connectorõV   aiohttp_socks not installed â€” SOCKS proxy %s ignored. Run: pip install aiohttp-socksÚproxy©r_   ra   Úaiohttp_socksrŸ   Úfrom_urlÚImportErrorÚloggerÚwarning©r›   rŸ   r¢   s      r   Úproxy_kwargs_for_botr¬     s¼   € ð ð Øˆ	Ø‡‚ÑÔ×#Ò# GÑ,Ô,ð ð	Ø4Ð4Ð4Ð4Ð4Ð4à&×/Ò/°	ÀÐ/ÑEÔEˆIØ Ð+Ð+øÝð 	ð 	ð 	Ý�NŠNð1àñô ð ð
 ˆIˆIˆIð	øøøð �YÐÐs   ­ A Á'A8Á7A8c                 ó  — | si i fS |                       ¦   «                              d¦  «        rS	 ddlm} |                     | d¬¦  «        }d|ii fS # t
          $ r" t                               d| ¦  «         i i fcY S w xY wi d| ifS )	uã  Build kwargs for standalone ``aiohttp.ClientSession`` with proxy.

    Returns ``(session_kwargs, request_kwargs)`` where:
      - SOCKS â†’ ``({"connector": ProxyConnector(...)}, {})``
      - HTTP  â†’ ``({}, {"proxy": url})``
      - None  â†’ ``({}, {})``

    Usage::

        sess_kw, req_kw = proxy_kwargs_for_aiohttp(proxy_url)
        async with aiohttp.ClientSession(**sess_kw) as session:
            async with session.get(url, **req_kw) as resp:
                ...
    r�   r   rž   Tr    r¢   r£   r¤   r¥   r«   s      r   Úproxy_kwargs_for_aiohttpr®   2  sÎ   € ð ð Ø�2ˆvˆØ‡‚ÑÔ×#Ò# GÑ,Ô,ð ð	Ø4Ð4Ð4Ð4Ð4Ð4à&×/Ò/°	ÀÐ/ÑEÔEˆIØ Ð+¨RÐ/Ð/øÝð 	ð 	ð 	Ý�NŠNð1àñô ð ð
 �r�6ˆMˆMˆMð	øøøð �˜Ð#Ð#Ð#s   ¯"A Á)A>Á=A>)Ú	dataclassÚfield)Údatetime)ÚPath)ÚDictÚListÚOptionalÚAnyÚCallableÚ	AwaitableÚTuple)ÚEnumr
   )ÚPlatformÚPlatformConfig)ÚSessionSourceÚbuild_session_key)Úget_hermes_dirz�Secure secret entry is not supported over messaging. Load this skill in the local CLI to be prompted, or add the key to ~/.hermes/.env manually.éP   ÚurlÚmax_lenc                 óî  — |dk    rdS | €dS t          | ¦  «        }|sdS 	 t          |¦  «        }n# t          $ r |d|…         cY S w xY w|j        rs|j        rl|j                             dd¦  «        d         }|j        › d|› �}|j        pd}|r1|dk    r+|                     dd¦  «        d         }|r|› d	|› �n|› d
�}n|}n|}t          |¦  «        |k    r|S |dk    rd|z  S |d|dz
  …         › d�S )z?Return a URL string safe for logs (no query/fragment/userinfo).r   rX   Nú@r   éÿÿÿÿrY   ú/z/.../z/...r5   rZ   z...)r]   r   rF   ÚschemeÚnetlocÚrsplitÚpathr   )	rÁ   rÂ   rf   rg   rÈ   ÚbaserÊ   ÚbasenameÚsafes	            r   Úsafe_url_for_logrÎ   g  sc  € à�!‚|€|Øˆrà
€{Øˆrå
ˆc‰(Œ(€CØð ØˆrðÝ˜#‘”ˆˆøÝð ð ð Ø�8�G�8Œ}ÐÐÐðøøøð „}ð ˜œð à”×%Ò% c¨1Ñ-Ô-¨bÔ1ˆØ”-Ð,Ð, FÐ,Ð,ˆØŒ{Ð ˜bˆØð 	�D˜C’K�KØ—{’{ 3¨Ñ*Ô*¨2Ô.ˆHØ/7ÐJ�dÐ+Ð+ Ð+Ð+Ð+À¸]¸]¸]ˆDˆDàˆDˆDàˆå
ˆ4�y„y�GÒÐØˆØ�!‚|€|Ø�W‰}ÐØ�<�G˜a‘K�<Ô Ð%Ð%Ð%Ð%s   ¡1 ±AÁAc              ƒ   óÀ   K  — | j         rP| j        rKt          | j        j        ¦  «        }ddlm}  ||¦  «        s#t          dt          |¦  «        › �¦  «        ‚dS dS dS )a%  Re-validate each redirect target to prevent redirect-based SSRF.

    Without this, an attacker can host a public URL that 302-redirects to
    http://169.254.169.254/ and bypass the pre-flight is_safe_url() check.

    Must be async because httpx.AsyncClient awaits response event hooks.
    r   ©Úis_safe_urlz.Blocked redirect to private/internal address: N)Úis_redirectÚnext_requestr]   rÁ   Útools.url_safetyrÑ   r#   rÎ   )ÚresponseÚredirect_urlrÑ   s      r   Ú_ssrf_redirect_guardr×   Œ  s™   è è € ð Ôð  Ô 5ð Ý˜8Ô0Ô4Ñ5Ô5ˆØ0Ð0Ð0Ð0Ð0Ð0Øˆ{˜<Ñ(Ô(ð 	ÝØaÕAQÐR^ÑA_ÔA_ÐaÐañô ð ð	ð ð ð ð	ð 	r   zcache/imagesÚimage_cachec                  óH   — t                                dd¬¦  «         t           S )zBReturn the image cache directory, creating it if it doesn't exist.T©ÚparentsÚexist_ok)ÚIMAGE_CACHE_DIRÚmkdir© r   r   Úget_image_cache_dirrà   ª  ó!   € å×Ò $°ÐÑ6Ô6Ð6ÝÐr   Údatac                 ó  — t          | ¦  «        dk     rdS | dd…         dk    rdS | dd…         dk    rdS | dd	…         d
v rdS | dd…         dk    rdS | dd…         dk    r#t          | ¦  «        dk    r| dd…         dk    rdS dS )zDReturn True if *data* starts with a known image magic-byte sequence.é   FNé   s   ‰PNG

Tr5   s   ÿØÿé   )s   GIF87as   GIF89ar
   s   BMs   RIFFé   s   WEBPr   )râ   s    r   Ú_looks_like_imagerè   °  sµ   € å
ˆ4�y„y�1‚}€}ØˆuØˆBˆQˆB„xÐ'Ò'Ð'ØˆtØˆBˆQˆB„x�?Ò"Ð"ØˆtØˆBˆQˆB„xÐ)Ð)Ð)ØˆtØˆBˆQˆB„x�5ÒÐØˆtØˆBˆQˆB„x�7ÒÐ�s 4™yœy¨Bš˜°4¸¸"¸´:ÀÒ3HÐ3HØˆtØˆ5r   ú.jpgÚextc                 óB  — t          | ¦  «        s5| dd…                              dd¬¦  «        }t          d|› d|›d�¦  «        ‚t          ¦   «         }d	t	          j        ¦   «         j        dd
…         › |› �}||z  }|                     | ¦  «         t          |¦  «        S )a›  
    Save raw image bytes to the cache and return the absolute file path.

    Args:
        data: Raw image bytes.
        ext:  File extension including the dot (e.g. ".jpg", ".png").

    Returns:
        Absolute path to the cached image file as a string.

    Raises:
        ValueError: If *data* does not look like a valid image (e.g. an HTML
            error page returned by the upstream server).
    NrÀ   zutf-8Úreplace)Úerrorsz$Refusing to cache non-image data as z (starts with: ú)Úimg_rç   )	rè   Údecoder#   rà   ÚuuidÚuuid4ÚhexÚwrite_bytesr]   )râ   rê   ÚsnippetÚ	cache_dirÚfilenameÚfilepaths         r   Úcache_image_from_bytesrù   Á  sÄ   € õ ˜TÑ"Ô"ð 
Ø�s˜�s”)×"Ò" 7°9Ð"Ñ=Ô=ˆÝð*°3ð *ð *Ø$ð*ð *ð *ñ
ô 
ð 	
õ $Ñ%Ô%€IØ2•d”j‘l”lÔ& s¨ sÔ+Ð2¨SÐ2Ð2€HØ˜8Ñ#€HØ×Ò˜ÑÔÐÝˆx‰=Œ=Ðr   Úretriesc              ƒ   óP  K  — ddl m}  || ¦  «        st          dt          | ¦  «        › �¦  «        ‚ddl}t          j        t          ¦  «        }|                     dddt          gi¬¦  «        4 ƒd{V —†	 }t          |d	z   ¦  «        D ]ö}	 |                     | d
ddœ¬¦  «        ƒ d{V —†}|                     ¦   «          t          |j        |¦  «        c cddd¦  «        ƒd{V —† S # |j        |j        f$ rƒ}	t#          |	|j        ¦  «        r|	j        j        dk     r‚ ||k     rQd|d	z   z  }
|                     d|d	z   |t          | ¦  «        |
|	¦  «         t+          j        |
¦  «        ƒ d{V —† Y d}	~	Œî‚ d}	~	ww xY w	 ddd¦  «        ƒd{V —† dS # 1 ƒd{V —†swxY w Y   dS )a@  
    Download an image from a URL and save it to the local cache.

    Retries on transient failures (timeouts, 429, 5xx) with exponential
    backoff so a single slow CDN response doesn't lose the media.

    Args:
        url: The HTTP/HTTPS URL to download from.
        ext: File extension including the dot (e.g. ".jpg", ".png").
        retries: Number of retry attempts on transient failures.

    Returns:
        Absolute path to the cached image file as a string.

    Raises:
        ValueError: If the URL targets a private/internal network (SSRF protection).
    r   rÐ   ú&Blocked unsafe URL (SSRF protection): Nç      >@TrÕ   ©r6   Úfollow_redirectsÚevent_hooksr   ú)Mozilla/5.0 (compatible; HermesAgent/1.0)zimage/*,*/*;q=0.8©z
User-AgentÚAccept©Úheadersé­  ç      ø?z*Media cache retry %d/%d for %s (%.1fs): %s)rÔ   rÑ   r#   rÎ   ÚhttpxÚloggingÚ	getLoggerÚ__name__ÚAsyncClientr×   ÚrangerJ   Úraise_for_statusrù   ÚcontentÚTimeoutExceptionÚHTTPStatusErrorr‹   rÕ   Ústatus_codeÚdebugÚasyncioÚsleep©rÁ   rê   rú   rÑ   r  Ú_logÚclientÚattemptrÕ   ÚexcÚwaits              r   Úcache_image_from_urlr  Ý  ó  è è € ð$ -Ð,Ð,Ð,Ð,Ð,Øˆ;�sÑÔð [ÝÐYÕBRÐSVÑBWÔBWÐYÐYÑZÔZÐZà€L€L€LÝÔ�XÑ&Ô&€Dà× Ò ØØØÕ"6Ð!7Ð8ð !ñ ô ð ð ð ð ð ð ð ð ð 
Ý˜W q™[Ñ)Ô)ð 	ð 	ˆGðØ!'§¢Øà&QØ"5ðð ð ",ñ "ô "ð ð ð ð ð ð �ð ×)Ò)Ñ+Ô+Ð+Ý-¨hÔ.>ÀÑDÔDÐDÐDðð ð ð ñ ô ð ð ð ð ð ð ð ð øð  Ô*¨EÔ,AÐBð ð ð Ý˜c 5Ô#8Ñ9Ô9ð ¸c¼lÔ>VÐY\Ò>\Ð>\ØØ˜WÒ$Ð$Ø '¨A¡+Ñ.�DØ—J’JØDØ !™ØÝ(¨Ñ-Ô-ØØñô ð õ "œ-¨Ñ-Ô-Ð-Ð-Ð-Ð-Ð-Ð-Ð-Ø�H�H�H�HØøøøøðøøøð	ðð ð ñ ô ð ð ð ð ð ð ð ð ð ð ð ð ð ð ð ð ð ð ð øøøð ð ð ð ð ð óD   Á7FÂAC*ÃFÃ*E>Ã;A8E9Å3FÅ8E9Å9E>Å>FÆ
FÆ"Fé   Úmax_age_hoursc                 óH  — ddl }t          ¦   «         }|                      ¦   «         | dz  z
  }d}|                     ¦   «         D ]^}|                     ¦   «         rH|                     ¦   «         j        |k     r+	 |                     ¦   «          |dz  }ŒN# t          $ r Y ŒZw xY wŒ_|S )zd
    Delete cached images older than *max_age_hours*.

    Returns the number of files removed.
    r   Né  r   )Útimerà   ÚiterdirÚis_fileÚstatÚst_mtimeÚunlinkr)   ©r   r#  rö   ÚcutoffÚremovedÚfs         r   Úcleanup_image_cacher-    s·   € ð €K€K€Kå#Ñ%Ô%€IØ�YŠY‰[Œ[˜M¨DÑ0Ñ1€FØ€GØ×ÒÑ Ô ð ð ˆØ�9Š9‰;Œ;ð 	˜1Ÿ6š6™8œ8Ô,¨vÒ5Ð5ðØ—’‘
”
�
Ø˜1‘��øÝð ð ð Ø�ðøøøøà€Nó   Á7BÂ
BÂBzcache/audioÚaudio_cachec                  óH   — t                                dd¬¦  «         t           S )zBReturn the audio cache directory, creating it if it doesn't exist.TrÚ   )ÚAUDIO_CACHE_DIRrÞ   rß   r   r   Úget_audio_cache_dirr2  7  rá   r   ú.oggc                 óº   — t          ¦   «         }dt          j        ¦   «         j        dd…         › |› �}||z  }|                     | ¦  «         t          |¦  «        S )a  
    Save raw audio bytes to the cache and return the absolute file path.

    Args:
        data: Raw audio bytes.
        ext:  File extension including the dot (e.g. ".ogg", ".mp3").

    Returns:
        Absolute path to the cached audio file as a string.
    Úaudio_Nrç   )r2  rñ   rò   ró   rô   r]   ©râ   rê   rö   r÷   rø   s        r   Úcache_audio_from_bytesr7  =  s]   € õ $Ñ%Ô%€IØ4�œ
™œÔ(¨¨"¨Ô-Ð4¨sÐ4Ð4€HØ˜8Ñ#€HØ×Ò˜ÑÔÐÝˆx‰=Œ=Ðr   c              ƒ   óP  K  — ddl m}  || ¦  «        st          dt          | ¦  «        › �¦  «        ‚ddl}t          j        t          ¦  «        }|                     dddt          gi¬¦  «        4 ƒd{V —†	 }t          |d	z   ¦  «        D ]ö}	 |                     | d
ddœ¬¦  «        ƒ d{V —†}|                     ¦   «          t          |j        |¦  «        c cddd¦  «        ƒd{V —† S # |j        |j        f$ rƒ}	t#          |	|j        ¦  «        r|	j        j        dk     r‚ ||k     rQd|d	z   z  }
|                     d|d	z   |t          | ¦  «        |
|	¦  «         t+          j        |
¦  «        ƒ d{V —† Y d}	~	Œî‚ d}	~	ww xY w	 ddd¦  «        ƒd{V —† dS # 1 ƒd{V —†swxY w Y   dS )aE  
    Download an audio file from a URL and save it to the local cache.

    Retries on transient failures (timeouts, 429, 5xx) with exponential
    backoff so a single slow CDN response doesn't lose the media.

    Args:
        url: The HTTP/HTTPS URL to download from.
        ext: File extension including the dot (e.g. ".ogg", ".mp3").
        retries: Number of retry attempts on transient failures.

    Returns:
        Absolute path to the cached audio file as a string.

    Raises:
        ValueError: If the URL targets a private/internal network (SSRF protection).
    r   rÐ   rü   Nrý   TrÕ   rþ   r   r  zaudio/*,*/*;q=0.8r  r  r  r  z*Audio cache retry %d/%d for %s (%.1fs): %s)rÔ   rÑ   r#   rÎ   r  r	  r
  r  r  r×   r  rJ   r  r7  r  r  r  r‹   rÕ   r  r  r  r  r  s              r   Úcache_audio_from_urlr9  O  r  r  zcache/videosÚvideo_cachez	video/mp4zvideo/quicktimez
video/webmzvideo/x-matroskazvideo/x-msvideo)ú.mp4ú.movú.webmú.mkvú.avic                  óH   — t                                dd¬¦  «         t           S )zBReturn the video cache directory, creating it if it doesn't exist.TrÚ   )ÚVIDEO_CACHE_DIRrÞ   rß   r   r   Úget_video_cache_dirrB  œ  rá   r   r;  c                 óº   — t          ¦   «         }dt          j        ¦   «         j        dd…         › |› �}||z  }|                     | ¦  «         t          |¦  «        S )zDSave raw video bytes to the cache and return the absolute file path.Úvideo_Nrç   )rB  rñ   rò   ró   rô   r]   r6  s        r   Úcache_video_from_bytesrE  ¢  s[   € å#Ñ%Ô%€IØ4�œ
™œÔ(¨¨"¨Ô-Ð4¨sÐ4Ð4€HØ˜8Ñ#€HØ×Ò˜ÑÔÐÝˆx‰=Œ=Ðr   zcache/documentsÚdocument_cachezapplication/pdfztext/markdownz
text/plainzapplication/zipzGapplication/vnd.openxmlformats-officedocument.wordprocessingml.documentzAapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheetzIapplication/vnd.openxmlformats-officedocument.presentationml.presentation)z.pdfz.mdz.txtz.logz.zipz.docxz.xlsxz.pptxc                  óH   — t                                dd¬¦  «         t           S )zEReturn the document cache directory, creating it if it doesn't exist.TrÚ   )ÚDOCUMENT_CACHE_DIRrÞ   rß   r   r   Úget_document_cache_dirrI  À  s!   € å×Ò T°DÐÑ9Ô9Ð9ÝÐr   r÷   c                 óâ  — t          ¦   «         }|rt          |¦  «        j        nd}|                     dd¦  «                             ¦   «         }|r|dv rd}dt          j        ¦   «         j        dd…         › d|› �}||z  }|                     ¦   «          	                    |                     ¦   «         ¦  «        st          d	|›�¦  «        ‚|                     | ¦  «         t          |¦  «        S )
aÜ  
    Save raw document bytes to the cache and return the absolute file path.

    The cached filename preserves the original human-readable name with a
    unique prefix: ``doc_{uuid12}_{original_filename}``.

    Args:
        data: Raw document bytes.
        filename: Original filename (e.g. "report.pdf").

    Returns:
        Absolute path to the cached document file as a string.

    Raises:
        ValueError: If the sanitized path escapes the cache directory.
    Údocumentú rX   )rZ   z..Údoc_Nrç   rO   zPath traversal rejected: )rI  r²   Únamerì   rH   rñ   rò   ró   ÚresolveÚis_relative_tor#   rô   r]   )râ   r÷   rö   Ú	safe_nameÚcached_namerø   s         r   Úcache_document_from_bytesrS  Æ  sö   € õ" 'Ñ(Ô(€Ià'/Ð?•�X‘”Ô#Ð#°Z€IØ×!Ò! &¨"Ñ-Ô-×3Ò3Ñ5Ô5€IØð ˜	 [Ð0Ð0Øˆ	Ø<�œ™œÔ)¨#¨2¨#Ô.Ð<Ð<°Ð<Ð<€KØ˜;Ñ&€Hà×ÒÑÔ×,Ò,¨Y×->Ò->Ñ-@Ô-@ÑAÔAð CÝÐA°XÐAÐAÑBÔBÐBØ×Ò˜ÑÔÐÝˆx‰=Œ=Ðr   c                 óH  — ddl }t          ¦   «         }|                      ¦   «         | dz  z
  }d}|                     ¦   «         D ]^}|                     ¦   «         rH|                     ¦   «         j        |k     r+	 |                     ¦   «          |dz  }ŒN# t          $ r Y ŒZw xY wŒ_|S )zg
    Delete cached documents older than *max_age_hours*.

    Returns the number of files removed.
    r   Nr"  r   )r#  rI  r$  r%  r&  r'  r(  r)   r)  s         r   Úcleanup_document_cacherU  æ  s·   € ð €K€K€Kå&Ñ(Ô(€IØ�YŠY‰[Œ[˜M¨DÑ0Ñ1€FØ€GØ×ÒÑ Ô ð ð ˆØ�9Š9‰;Œ;ð 	˜1Ÿ6š6™8œ8Ô,¨vÒ5Ð5ðØ—’‘
”
�
Ø˜1‘��øÝð ð ð Ø�ðøøøøà€Nr.  c                   ó6   — e Zd ZdZdZdZdZdZdZdZ	dZ
d	Zd
ZdS )ÚMessageTypezTypes of incoming messages.r7   ÚlocationÚphotoÚvideoÚaudioÚvoicerK  ÚstickerÚcommandN)r  Ú
__module__Ú__qualname__Ú__doc__ÚTEXTÚLOCATIONÚPHOTOÚVIDEOÚAUDIOÚVOICEÚDOCUMENTÚSTICKERÚCOMMANDrß   r   r   rW  rW  û  sA   € € € € € Ø%Ð%Ø€DØ€HØ€EØ€EØ€EØ€EØ€HØ€GØ€G€G€Gr   rW  c                   ó   — e Zd ZdZdZdZdZdS )ÚProcessingOutcomez=Result classification for message-processing lifecycle hooks.ÚsuccessÚfailureÚ	cancelledN)r  r_  r`  ra  ÚSUCCESSÚFAILUREÚ	CANCELLEDrß   r   r   rl  rl    s#   € € € € € ØGÐGà€GØ€GØ€I€I€Ir   rl  c                   óÆ  — e Zd ZU dZeed<   ej        Zeed<   dZ	e
ed<   dZeed<   dZee         ed<   dZee         ed<    ee¬	¦  «        Zee         ed
<    ee¬	¦  «        Zee         ed<   dZee         ed<   dZee         ed<   dZeeee         z           ed<   dZee         ed<   dZeed<    eej        ¬	¦  «        Zeed<   defd„Zdee         fd„Z defd„Z!dS )ÚMessageEventzi
    Incoming message from a platform.
    
    Normalized representation that all adapters produce.
    r7   Úmessage_typeNÚsourceÚraw_messageÚ
message_idÚplatform_update_id)Údefault_factoryÚ
media_urlsÚmedia_typesÚreply_to_message_idÚreply_to_textÚ
auto_skillÚchannel_promptFÚinternalÚ	timestampr   c                 ó6   — | j                              d¦  «        S )z8Check if this is a command message (e.g., /new, /reset).rÆ   )r7   ra   ©Úselfs    r   Ú
is_commandzMessageEvent.is_commandC  s   € àŒy×#Ò# CÑ(Ô(Ð(r   c                 ó  — |                       ¦   «         sdS | j                             d¬¦  «        }|r"|d         dd…                              ¦   «         nd}|r d|v r|                     dd¦  «        d         }|rd|v rdS |S )z2Extract command name if this is a command message.Nr   ©Úmaxsplitr   rÄ   rÆ   )r†  r7   rx   r_   )r…  Úpartsrf   s      r   Úget_commandzMessageEvent.get_commandG  s—   € à�ŠÑ Ô ð 	Ø�4à”	—’¨�Ñ+Ô+ˆØ&+Ð5ˆe�AŒh�q�r�rŒl× Ò Ñ"Ô"Ð"°ˆØð 	'�3˜#�:�:Ø—)’)˜C Ñ#Ô# AÔ&ˆCàð 	�3˜#�:�:Ø�4Øˆ
r   c                 ó(  — |                       ¦   «         s| j        S | j                             d¬¦  «        }t          |¦  «        dk    r|d         nd}|                     dd¦  «                             dd¦  «                             dd¦  «        }|S )	z"Get the arguments after a command.r   rˆ  rX   u   â€”â€”z--u   â€”u   â€“Ú-)r†  r7   rx   r   rì   )r…  rŠ  Úargss      r   Úget_command_argszMessageEvent.get_command_argsU  s†   € à�ŠÑ Ô ð 	Ø”9ÐØ”	—’¨�Ñ+Ô+ˆÝ˜u™:œ:¨š>˜>ˆu�QŒxˆx¨rˆà�|Š|˜N¨DÑ1Ô1×9Ò9¸(ÀDÑIÔI×QÒQÐRZÐ\_Ñ`Ô`ˆØˆr   )"r  r_  r`  ra  r]   Ú__annotations__rW  rb  ru  rv  r½   rw  r¶   rx  rµ   ry  rc   r°   rŒ   r{  r´   r|  r}  r~  r  r€  r�  Úboolr±   Únowr‚  r†  r‹  r�  rß   r   r   rt  rt    s¹  € € € € € € ðð ð €I€I�IØ +Ô 0€L�+Ð0Ð0Ñ0ð !€FˆMÐ Ð Ñ ð €K�ÐÐÑØ $€J�˜”Ð$Ð$Ñ$ð )-Ð˜ œÐ,Ð,Ñ,ð "˜E°$Ð7Ñ7Ô7€J��S”	Ð7Ð7Ñ7Ø"˜U°4Ð8Ñ8Ô8€K��c”Ð8Ð8Ñ8ð *.Ð˜ #œÐ-Ð-Ñ-Ø#'€M�8˜C”=Ð'Ð'Ñ'ð -1€J�˜˜t Cœy™Ô)Ð0Ð0Ñ0ð %)€N�H˜S”MÐ(Ð(Ñ(ð €HˆdÐÐÑð  ˜%°´Ð=Ñ=Ô=€IˆxÐ=Ð=Ñ=ð)˜Dð )ð )ð )ð )ð˜X cœ]ð ð ð ð ð #ð ð ð ð ð ð r   rt  c                   ón   — e Zd ZU dZeed<   dZee         ed<   dZ	ee         ed<   dZ
eed<   dZeed<   dS )	Ú
SendResultzResult of sending a message.rm  Nrx  ÚerrorÚraw_responseFÚ	retryable)r  r_  r`  ra  r‘  r�  rx  rµ   r]   r•  r–  r¶   r—  rß   r   r   r”  r”  `  si   € € € € € € à&Ð&Ø€M€M�MØ $€J�˜”Ð$Ð$Ñ$Ø€Eˆ8�CŒ=ÐÐÑØ€L�#ÐÐÑØ€IˆtÐÐÑÐÐr   r”  F)Ú
merge_textÚpending_messagesÚsession_keyÚeventr˜  c                óè  — |                       |¦  «        }|�rÔt          |dd¦  «        t          j        k    }|j        t          j        k    }t          |j        ¦  «        }t          |j        ¦  «        }|rs|rq|j                             |j        ¦  «         |j                             |j        ¦  «         |j	        r*t                               |j	        |j	        ¦  «        |_	        dS |s|rœ|r>|j                             |j        ¦  «         |j                             |j        ¦  «         |j	        r>|j	        r+t                               |j	        |j	        ¦  «        |_	        n|j	        |_	        |s|rt          j        |_        dS |rat          |dd¦  «        t          j        k    rB|j        t          j        k    r-|j	        r$|j	        r|j	        › d|j	        › �n|j	        |_	        dS || |<   dS )aä  Store or merge a pending event for a session.

    Photo bursts/albums often arrive as multiple near-simultaneous PHOTO
    events. Merge those into the existing queued event so the next turn sees
    the whole burst.

    When ``merge_text`` is enabled, rapid follow-up TEXT events are appended
    instead of replacing the pending turn. This is used for Telegram bursty
    follow-ups so a multi-part user thought is not silently truncated to only
    the last queued fragment.
    ru  NÚ
)rJ   r"   rW  rd  ru  r‘  r{  rw   r|  r7   ÚBasePlatformAdapterÚ_merge_captionrb  )	r™  rš  r›  r˜  ÚexistingÚexisting_is_photoÚincoming_is_photoÚexisting_has_mediaÚincoming_has_medias	            r   Úmerge_pending_message_eventr¥  j  s  € ð$  ×#Ò# KÑ0Ô0€HØñ !Ý# H¨n¸dÑCÔCÅ{ÔGXÒXÐØ!Ô.µ+Ô2CÒCÐÝ! (Ô"5Ñ6Ô6ÐÝ! %Ô"2Ñ3Ô3Ðàð 	Ð!2ð 	ØÔ×&Ò& uÔ'7Ñ8Ô8Ð8ØÔ ×'Ò'¨Ô(9Ñ:Ô:Ð:ØŒzð ^Ý 3× BÒ BÀ8Ä=ÐRWÔR\Ñ ]Ô ]�”ØˆFàð 	Ð!3ð 	Ø!ð ?ØÔ#×*Ò*¨5Ô+;Ñ<Ô<Ð<ØÔ$×+Ò+¨EÔ,=Ñ>Ô>Ð>ØŒzð /Ø”=ð /Ý$7×$FÒ$FÀxÄ}ÐV[ÔV`Ñ$aÔ$a�H”M�Mà$)¤J�H”MØ ð :Ð$5ð :Ý(3Ô(9�Ô%ØˆFð ð	å˜ .°$Ñ7Ô7½;Ô;KÒKÐKØÔ"¥kÔ&6Ò6Ð6àŒzð bØDLÄMÐ a 8¤=Ð @Ð @°E´JÐ @Ð @Ð @ÐW\ÔWa�”ØˆFà$)Ð�[Ñ!Ð!Ð!r   )	ÚconnecterrorÚconnectionerrorÚconnectionresetÚconnectionrefusedÚconnecttimeoutr„   zbroken pipeÚremotedisconnectedÚeoferrorÚconfig_extraÚ
channel_idÚ	parent_idc                 óô   — |                       d¦  «        pi }t          |t          ¦  «        sdS ||fD ]D}|sŒ|                      |¦  «        }|€Œt          |¦  «                             ¦   «         }|r|c S ŒEdS )a–  Resolve a per-channel ephemeral prompt from platform config.

    Looks up ``channel_prompts`` in the adapter's ``config.extra`` dict.
    Prefers an exact match on *channel_id*; falls back to *parent_id*
    (useful for forum threads / child channels inheriting a parent prompt).

    Returns the prompt string, or None if no match is found.  Blank/whitespace-
    only prompts are treated as absent.
    Úchannel_promptsN)rJ   r‹   Údictr]   rH   )r­  r®  r¯  ÚpromptsrN   Úprompts         r   Úresolve_channel_promptrµ  »  sž   € ð ×ÒÐ0Ñ1Ô1Ð7°R€GÝ�g�tÑ$Ô$ð Øˆtà˜IÐ&ð ð ˆØð 	ØØ—’˜SÑ!Ô!ˆØˆ>ØÝ�V‘”×"Ò"Ñ$Ô$ˆØð 	ØˆMˆMˆMð	àˆ4r   c                   óÀ	  — e Zd ZU dZdedefd„Zedefd„¦   «         Z	ede
e         fd„¦   «         Zede
e         fd„¦   «         Zedefd	„¦   «         Zd
edefd„Zded ged         dz  f         ddfd„ZdŽd„ZdŽd„Zdedededdfd„ZdŽd„Zdedededefd„ZdŽd„Zedefd„¦   «         Zedefd„¦   «         Zdeddfd„Zde
eeegee         f                  ddfd„Zdeddfd „Z e!defd!„¦   «         Z"e!dŽd"„¦   «         Z#e!	 	 d�d
ed#ed$e
e         d%e
e$eef                  de%f
d&„¦   «         Z&d'Z'ee(d(<   d'd)œd
ed*ed#ed+ede%f
d,„Z)d�d
eddfd-„Z*d
eddfd.„Z+	 	 	 d‘d
ed/ed0e
e         d$e
e         d%e
e$eef                  de%fd1„Z,	 	 	 d‘d
ed2ed0e
e         d$e
e         d%e
e$eef                  de%fd3„Z-e.d4edefd5„¦   «         Z/e.d#ede0e1e0eef                  ef         fd6„¦   «         Z2	 	 d�d
ed7ed0e
e         d$e
e         de%f
d8„Z3d
ed7ede%fd9„Z4	 	 d�d
ed:ed0e
e         d$e
e         de%f
d;„Z5	 	 	 d‘d
ed<ed0e
e         d=e
e         d$e
e         de%fd>„Z6	 	 d�d
ed?ed0e
e         d$e
e         de%f
d@„Z7e.d#ede0e1e0eef                  ef         fdA„¦   «         Z8e.d#ede0e1e         ef         fdB„¦   «         Z9	 	 	 d’d
edDe:dEe;j<        dz  ddfdF„Z=d
eddfdG„Z>d
eddfdH„Z?dIed
eddfdJ„Z@ddKœdIedLedMeAdz  ddfdN„ZBddKœdIedMeAdz  dedz  fdO„ZCdPeddfdQ„ZDdPedReEddfdS„ZFdTedUedVeddfdW„ZGe.dXe
e         defdY„¦   «         ZHe.dXe
e         defdZ„¦   «         ZI	 	 	 	 d“d
ed#ed$e
e         d%ed\eAd]e:dd^fd_„ZJe.d`e
e         daedefdb„¦   «         ZKddcœdIedde
e;j<                 ddfde„ZLdIedefdf„ZMdIedefdg„ZNddhœdPedIedie
e;j<                 defdj„ZOdkdkdlœdIedmedneddfdo„ZPdIedpe;j<        ddfdq„ZQdPedIedreddfds„ZRdPeddfdt„ZSe.de:fdu„¦   «         ZTdPedIeddfdv„ZUdŽdw„ZVdIedefdx„ZWdIede
e         fdy„ZX	 	 	 	 	 	 	 	 	 	 	 	 d”d
ed{e
e         d|ed}e
e         d~e
e         de
e         d€e
e         d�e
e         d‚e
e         dƒed„e
e         d…e
e         d*e
e         deYfd†„ZZe!d
ede$eef         fd‡„¦   «         Z[d#edefdˆ„Z\e.	 	 d•d#edŠeAd‹e
dŒ         de1e         fd�„¦   «         Z]dS )–rž  zÙ
    Base class for platform adapters.
    
    Subclasses implement platform-specific logic for:
    - Connecting and authenticating
    - Receiving messages
    - Sending messages/responses
    - Handling media
    ÚconfigrB   c                 óˆ  — || _         || _        d | _        d| _        d | _        d | _        d| _        d | _        i | _        i | _	        i | _
        t          ¦   «         | _        i | _        t          ¦   «         | _        d | _        d| _        t          ¦   «         | _        t          ¦   «         | _        t          ¦   «         | _        d S )NFT)r·  rB   Ú_message_handlerÚ_runningÚ_fatal_error_codeÚ_fatal_error_messageÚ_fatal_error_retryableÚ_fatal_error_handlerÚ_active_sessionsÚ_pending_messagesÚ_session_tasksÚsetÚ_background_tasksÚ_post_delivery_callbacksÚ_expected_cancelled_tasksÚ_busy_session_handlerÚ_auto_tts_defaultÚ_auto_tts_enabled_chatsÚ_auto_tts_disabled_chatsÚ_typing_paused)r…  r·  rB   s      r   Ú__init__zBasePlatformAdapter.__init__ä  s»   € ØˆŒØ ˆŒØ:>ˆÔØˆŒØ04ˆÔØ37ˆÔ!Ø&*ˆÔ#ØimˆÔ!ð ;=ˆÔØ:<ˆÔØ79ˆÔõ 58±E´EˆÔð 9;ˆÔ%Ý<?¹E¼EˆÔ&Ø_cˆÔ"ð (-ˆÔÝ,/©E¬EˆÔ$Ý-0©U¬UˆÔ%õ $'¡5¤5ˆÔÐÐr   r   c                 ó   — | j         d uS ro   ©r¼  r„  s    r   Úhas_fatal_errorz#BasePlatformAdapter.has_fatal_error  s   € àÔ(°Ð4Ð4r   c                 ó   — | j         S ro   rÍ  r„  s    r   Úfatal_error_messagez'BasePlatformAdapter.fatal_error_message  s   € àÔ(Ð(r   c                 ó   — | j         S ro   )r»  r„  s    r   Úfatal_error_codez$BasePlatformAdapter.fatal_error_code  s   € àÔ%Ð%r   c                 ó   — | j         S ro   )r½  r„  s    r   Úfatal_error_retryablez)BasePlatformAdapter.fatal_error_retryable#  s   € àÔ*Ð*r   Úchat_idc                 óV   — || j         v rdS || j        v rdS t          | j        ¦  «        S )ue  Whether auto-TTS on voice input should fire for ``chat_id``.

        Decision layers (Issue #16007):
          1. Explicit ``/voice on`` or ``/voice tts`` â†’ always fire (even if
             ``voice.auto_tts`` is False).
          2. Explicit ``/voice off`` â†’ never fire.
          3. Fall back to the global ``voice.auto_tts`` config default.
        TF)rÈ  rÉ  r‘  rÇ  ©r…  rÕ  s     r   Ú_should_auto_tts_for_chatz-BasePlatformAdapter._should_auto_tts_for_chat'  s;   € ð �dÔ2Ð2Ð2Ø�4Ø�dÔ3Ð3Ð3Ø�5Ý�DÔ*Ñ+Ô+Ð+r   ÚhandlerNc                 ó   — || _         d S ro   )r¾  ©r…  rÙ  s     r   Úset_fatal_error_handlerz+BasePlatformAdapter.set_fatal_error_handler6  s   € Ø$+ˆÔ!Ð!Ð!r   c                 ó    — d| _         d | _        d | _        d| _        	 ddlm}  || j        j        dd d ¬¦  «         d S # t          $ r Y d S w xY w)NTr   ©Úwrite_runtime_statusÚ	connected©rB   Úplatform_stateÚ
error_codeÚerror_message©	rº  r»  r¼  r½  Úgateway.statusrß  rB   rV   rF   ©r…  rß  s     r   Ú_mark_connectedz#BasePlatformAdapter._mark_connected9  s�   € ØˆŒØ!%ˆÔØ$(ˆÔ!Ø&*ˆÔ#ð	Ø;Ð;Ð;Ð;Ð;Ð;Ø Ð ¨$¬-Ô*=ÈkÐfjÐz~ÐÑÔÐÐÐøÝð 	ð 	ð 	ØˆDˆDð	øøøó   ž? ¿
AÁAc                 óˆ   — d| _         | j        rd S 	 ddlm}  || j        j        dd d ¬¦  «         d S # t          $ r Y d S w xY w)NFr   rÞ  Údisconnectedrá  )rº  rÎ  ræ  rß  rB   rV   rF   rç  s     r   Ú_mark_disconnectedz&BasePlatformAdapter._mark_disconnectedD  s�   € ØˆŒØÔð 	ØˆFð	Ø;Ð;Ð;Ð;Ð;Ð;Ø Ð ¨$¬-Ô*=ÈnÐimð  ~Bð  Cñ  Cô  Cð  Cð  Cð  CøÝð 	ð 	ð 	ØˆDˆDð	øøøs   ’3 ³
AÁ AÚcodeÚmessager—  c                ó    — d| _         || _        || _        || _        	 ddlm}  || j        j        d||¬¦  «         d S # t          $ r Y d S w xY w)NFr   rÞ  Úfatalrá  rå  )r…  rí  rî  r—  rß  s        r   Ú_set_fatal_errorz$BasePlatformAdapter._set_fatal_errorN  s‘   € ØˆŒØ!%ˆÔØ$+ˆÔ!Ø&/ˆÔ#ð		Ø;Ð;Ð;Ð;Ð;Ð;Ø Ð ØœÔ,Ø&ØØ%ð	ñ ô ð ð ð øõ ð 	ð 	ð 	ØˆDˆDð	øøøré  c              ƒ   ór   K  — | j         }|sd S  || ¦  «        }t          j        |¦  «        r
|ƒ d {V —† d S d S ro   )r¾  r  Úiscoroutine)r…  rÙ  Úresults      r   Ú_notify_fatal_errorz'BasePlatformAdapter._notify_fatal_error^  sZ   è è € ØÔ+ˆØð 	ØˆFØ�˜‘”ˆÝÔ˜vÑ&Ô&ð 	ØˆLˆLˆLˆLˆLˆLˆLˆLˆLð	ð 	r   ÚscopeÚidentityÚresource_descc                 óf  — ddl m} || _        || _         |||d| j        j        i¬¦  «        \  }}|rdS t          |t          ¦  «        r|                     d¦  «        nd}|› d�|rd	|› d
�ndz   dz   }t           
                    d| j        |¦  «         |                      |› d�|d¬¦  «         dS )z@Acquire a scoped lock for this adapter. Returns True on success.r   )Úacquire_scoped_lockrB   ©ÚmetadataTÚpidNz already in usez (PID rî   rX   z. Stop the other gateway first.z[%s] %sÚ_lockF)r—  )ræ  rú  Ú_platform_lock_scopeÚ_platform_lock_identityrB   rV   r‹   r²  rJ   r©   r•  rN  rñ  )	r…  rö  r÷  rø  rú  Úacquiredr   Ú	owner_pidrî  s	            r   Ú_acquire_platform_lockz*BasePlatformAdapter._acquire_platform_lockf  sü   € à6Ð6Ð6Ð6Ð6Ð6Ø$)ˆÔ!Ø'/ˆÔ$Ø0Ð0Ø�8 z°4´=Ô3FÐ&Gð
ñ 
ô 
Ñˆ�(ð ð 	Ø�4Ý+5°hÅÑ+EÔ+EÐO�H—L’L Ñ'Ô'Ð'È4ˆ	àÐ-Ð-Ð-Ø(1Ð9Ð$˜	Ð$Ð$Ð$Ð$°rñ;à/ñ0ð 	õ
 	�Š�Y ¤	¨7Ñ3Ô3Ð3Ø×Ò ˜o˜o˜o¨wÀ%ÐÑHÔHÐHØˆur   c                 ól   — t          | dd¦  «        }|sdS ddlm}  || j        |¦  «         d| _        dS )z;Release the scoped lock acquired by _acquire_platform_lock.r   Nr   )Úrelease_scoped_lock)r"   ræ  r  rÿ  r   )r…  r÷  r  s      r   Ú_release_platform_lockz*BasePlatformAdapter._release_platform_lockz  sW   € å˜4Ð!:¸DÑAÔAˆØð 	ØˆFØ6Ð6Ð6Ð6Ð6Ð6ØÐ˜DÔ5°xÑ@Ô@Ð@Ø'+ˆÔ$Ð$Ð$r   c                 ó>   — | j         j                             ¦   «         S )z%Human-readable name for this adapter.)rB   rV   Útitler„  s    r   rN  zBasePlatformAdapter.nameƒ  s   € ð Œ}Ô"×(Ò(Ñ*Ô*Ð*r   c                 ó   — | j         S )z(Check if adapter is currently connected.)rº  r„  s    r   Úis_connectedz BasePlatformAdapter.is_connectedˆ  s   € ð Œ}Ðr   c                 ó   — || _         dS )z¤
        Set the handler for incoming messages.
        
        The handler receives a MessageEvent and should return
        an optional response string.
        N)r¹  rÛ  s     r   Úset_message_handlerz'BasePlatformAdapter.set_message_handler�  s   € ð !(ˆÔÐÐr   c                 ó   — || _         dS )zESet an optional handler for messages arriving during active sessions.N)rÆ  rÛ  s     r   Úset_busy_session_handlerz,BasePlatformAdapter.set_busy_session_handler–  s   € à%,ˆÔ"Ð"Ð"r   Úsession_storec                 ó   — || _         dS )a  
        Set the session store for checking active sessions.
        
        Used by adapters that need to check if a thread/conversation
        has an active session before processing messages (e.g., Slack
        thread replies without explicit mentions).
        N)Ú_session_store)r…  r  s     r   Úset_session_storez%BasePlatformAdapter.set_session_storeš  s   € ð ,ˆÔÐÐr   c              ƒ   ó
   K  — dS )zƒ
        Connect to the platform and start receiving messages.
        
        Returns True if connection was successful.
        Nrß   r„  s    r   ÚconnectzBasePlatformAdapter.connect¤  ó   è è € ð 	ˆr   c              ƒ   ó
   K  — dS )zDisconnect from the platform.Nrß   r„  s    r   Ú
disconnectzBasePlatformAdapter.disconnect­  s   è è € ð 	ˆr   r  Úreply_torü  c              ƒ   ó
   K  — dS )ar  
        Send a message to a chat.
        
        Args:
            chat_id: The chat/channel ID to send to
            content: Message content (may be markdown)
            reply_to: Optional message ID to reply to
            metadata: Additional platform-specific options
        
        Returns:
            SendResult with success status and message ID
        Nrß   )r…  rÕ  r  r  rü  s        r   ÚsendzBasePlatformAdapter.send²  s   è è € ð( 	ˆr   FÚREQUIRES_EDIT_FINALIZE)Úfinalizerx  r  c             ƒ   ó(   K  — t          dd¬¦  «        S )uL  
        Edit a previously sent message. Optional â€” platforms that don't
        support editing return success=False and callers fall back to
        sending a new message.

        ``finalize`` signals that this is the last edit in a streaming
        sequence.  Most platforms (Telegram, Slack, Discord, Matrix,
        etc.) treat it as a no-op because their edit APIs have no notion
        of message lifecycle state â€” an edit is an edit.  Platforms that
        render streaming updates with a distinct "in progress" state and
        require explicit closure (e.g. rich card / AI assistant surfaces
        such as DingTalk AI Cards) use it to finalize the message and
        transition the UI out of the streaming indicator â€” those should
        also set ``REQUIRES_EDIT_FINALIZE = True`` so callers route a
        final edit through even when content is unchanged.  Callers
        should set ``finalize=True`` on the final edit of a streamed
        response (typically when ``got_done`` fires in the stream
        consumer) and leave it ``False`` on intermediate edits.
        FzNot supported)rm  r•  )r”  )r…  rÕ  rx  r  r  s        r   Úedit_messagez BasePlatformAdapter.edit_messageÐ  s   è è € õ6  %¨Ð?Ñ?Ô?Ð?r   c              ƒ   ó
   K  — dS )zÊ
        Send a typing indicator.
        
        Override in subclasses if the platform supports it.
        metadata: optional dict with platform-specific context (e.g. thread_id for Slack).
        Nrß   )r…  rÕ  rü  s      r   Úsend_typingzBasePlatformAdapter.send_typingí  r  r   c              ƒ   ó
   K  — dS )zÕStop a persistent typing indicator (if the platform uses one).

        Override in subclasses that start background typing loops.
        Default is a no-op for platforms with one-shot typing indicators.
        Nrß   r×  s     r   Ústop_typingzBasePlatformAdapter.stop_typingö  s   è è € ð 	ˆr   Ú	image_urlÚcaptionc              ƒ   óX   K  — |r|› d|› �n|}|                       |||¬¦  «        ƒ d{V —†S )zð
        Send an image natively via the platform API.
        
        Override in subclasses to send images as proper attachments
        instead of plain-text URLs. Default falls back to sending the
        URL as a text message.
        r�  ©rÕ  r  r  N©r  )r…  rÕ  r#  r$  r  rü  r7   s          r   Ú
send_imagezBasePlatformAdapter.send_imageþ  sO   è è € ð  -4ÐB�'Ð(Ð(˜YÐ(Ð(Ð(¸ˆØ—Y’Y w¸Àx�YÑPÔPÐPÐPÐPÐPÐPÐPÐPr   Úanimation_urlc              ƒ   óF   K  — |                       |||||¬¦  «        ƒ d{V —†S )zû
        Send an animated GIF natively via the platform API.
        
        Override in subclasses to send GIFs as proper animations
        (e.g., Telegram send_animation) so they auto-play inline.
        Default falls back to send_image.
        )rÕ  r#  r$  r  rü  N)r(  )r…  rÕ  r)  r$  r  rü  s         r   Úsend_animationz"BasePlatformAdapter.send_animation  sX   è è € ð —_’_¨WÀÐW^Ðiqð  }E�_ñ  Fô  Fð  Fð  Fð  Fð  Fð  Fð  Fð  	Fr   rÁ   c                 ó†   — |                       ¦   «                              d¦  «        d         }|                     d¦  «        S )z=Check if a URL points to an animated GIF (vs a static image).ú?r   ú.gif)r_   rx   r€   )rÁ   r_   s     r   Ú_is_animation_urlz%BasePlatformAdapter._is_animation_url"  s6   € ð —	’	‘”×!Ò! #Ñ&Ô& qÔ)ˆØ�~Š~˜fÑ%Ô%Ð%r   c                 ó\  ‡‡	— g }| }d}t          j        || ¦  «        D ]^}|                     d¦  «        }|                     d¦  «        Š	t          ˆ	fd„dD ¦   «         ¦  «        r|                     ‰	|f¦  «         Œ_d}t          j        || ¦  «        D ].}|                     d¦  «        Š	|                     ‰	df¦  «         Œ/|red„ |D ¦   «         Šˆfd	„}t          j        |||¦  «        }t          j        |||¦  «        }t          j        d
d|¦  «                             ¦   «         }||fS )aÞ  
        Extract image URLs from markdown and HTML image tags in a response.
        
        Finds patterns like:
        - ![alt text](https://example.com/image.png)
        - <img src="https://example.com/image.png">
        - <img src="https://example.com/image.png"></img>
        
        Args:
            content: The response text to scan.
        
        Returns:
            Tuple of (list of (url, alt_text) pairs, cleaned content with image tags removed).
        z$!\[([^\]]*)\]\((https?://[^\s\)]+)\)r   r
   c              3   ó’   •K  — | ]A}‰                      ¦   «                              |¦  «        p|‰                      ¦   «         v V — ŒBd S ro   )r_   r€   )rq   rê   rÁ   s     €r   rs   z5BasePlatformAdapter.extract_images.<locals>.<genexpr>A  sc   øè è € ð mð mÀs�3—9’9‘;”;×'Ò'¨Ñ,Ô,ÐB°°s·y²y±{´{Ð0Bð mð mð mð mð mð mr   )ú.pngré   ú.jpegr.  ú.webpz	fal.mediazfal-cdnzreplicate.deliveryzA<img\s+src=["\']?(https?://[^\s"\'<>]+)["\']?\s*/?>\s*(?:</img>)?rX   c                 ó   — h | ]\  }}|’ŒS rß   rß   )rq   rÁ   rO   s      r   ú	<setcomp>z5BasePlatformAdapter.extract_images.<locals>.<setcomp>M  s   € Ð7Ð7Ð7¡f c¨1˜cÐ7Ð7Ð7r   c                 ó¤   •— | j         dk    r|                      d¦  «        n|                      d¦  «        }|‰v rdn|                      d¦  «        S )Nr
   r   rX   r   )Ú	lastindexÚgroup)ÚmatchrÁ   Úextracted_urlss     €r   Ú_remove_if_extractedz@BasePlatformAdapter.extract_images.<locals>._remove_if_extractedN  sJ   ø€ Ø(-¬¸1Ò(<Ð(<�e—k’k !‘n”n�nÀ%Ç+Â+ÈaÁ.Ä.�Ø  NÐ2Ð2�r�r¸¿ºÀA¹¼ÐFr   ú\n{3,}ú

)ÚreÚfinditerr9  r�   ÚappendÚsubrH   )
r  ÚimagesÚcleanedÚ
md_patternr:  Úalt_textÚhtml_patternr<  r;  rÁ   s
           @@r   Úextract_imagesz"BasePlatformAdapter.extract_images(  s‚  øø€ ð  ˆØˆð =ˆ
Ý”[ ¨WÑ5Ô5ð 	/ð 	/ˆEØ—{’{ 1‘~”~ˆHØ—+’+˜a‘.”.ˆCåð mð mð mð mØkðmñ mô mñ mô mð /à—’˜s H˜oÑ.Ô.Ð.øð \ˆÝ”[ ¨wÑ7Ô7ð 	%ð 	%ˆEØ—+’+˜a‘.”.ˆCØ�MŠM˜3 ˜)Ñ$Ô$Ð$Ð$ð ð 	AØ7Ð7°Ð7Ñ7Ô7ˆNðGð Gð Gð Gð Gõ ”f˜ZÐ)=¸wÑGÔGˆGÝ”f˜\Ð+?ÀÑIÔIˆGå”f˜Y¨°Ñ8Ô8×>Ò>Ñ@Ô@ˆGà�wˆÐr   Ú
audio_pathc              ‹   ó^   K  — d|› �}|r|› d|› �}|                       |||¬¦  «        ƒ d{V —†S )a
  
        Send an audio file as a native voice message via the platform API.
        
        Override in subclasses to send audio as voice bubbles (Telegram)
        or file attachments (Discord). Default falls back to sending the
        file path as text.
        u   ðŸ”Š Audio: r�  r&  Nr'  )r…  rÕ  rI  r$  r  Úkwargsr7   s          r   Ú
send_voicezBasePlatformAdapter.send_voiceX  sZ   è è € ð +˜jÐ*Ð*ˆØð 	(ØÐ'Ð' Ð'Ð'ˆDØ—Y’Y w¸Àx�YÑPÔPÐPÐPÐPÐPÐPÐPÐPr   c              ‹   ó2   K  —  | j         d||dœ|¤Žƒ d{V —†S )z½
        Play auto-TTS audio for voice replies.

        Override in subclasses for invisible playback (e.g. Web UI).
        Default falls back to send_voice (shows audio player).
        )rÕ  rI  Nrß   )rL  )r…  rÕ  rI  rK  s       r   Úplay_ttszBasePlatformAdapter.play_ttsl  s9   è è € ð %�T”_ÐV¨WÀÐVÐVÈvÐVÐVÐVÐVÐVÐVÐVÐVÐVr   Ú
video_pathc              ‹   ó^   K  — d|› �}|r|› d|› �}|                       |||¬¦  «        ƒ d{V —†S )zÃ
        Send a video natively via the platform API.

        Override in subclasses to send videos as inline playable media.
        Default falls back to sending the file path as text.
        u   ðŸŽ¬ Video: r�  r&  Nr'  )r…  rÕ  rO  r$  r  rK  r7   s          r   Ú
send_videozBasePlatformAdapter.send_videoz  sZ   è è € ð +˜jÐ*Ð*ˆØð 	(ØÐ'Ð' Ð'Ð'ˆDØ—Y’Y w¸Àx�YÑPÔPÐPÐPÐPÐPÐPÐPÐPr   Ú	file_pathÚ	file_namec              ‹   ó^   K  — d|› �}|r|› d|› �}|                       |||¬¦  «        ƒ d{V —†S )zÍ
        Send a document/file natively via the platform API.

        Override in subclasses to send files as downloadable attachments.
        Default falls back to sending the file path as text.
        u   ðŸ“Ž File: r�  r&  Nr'  )r…  rÕ  rR  r$  rS  r  rK  r7   s           r   Úsend_documentz!BasePlatformAdapter.send_document�  sZ   è è € ð )˜YÐ(Ð(ˆØð 	(ØÐ'Ð' Ð'Ð'ˆDØ—Y’Y w¸Àx�YÑPÔPÐPÐPÐPÐPÐPÐPÐPr   Ú
image_pathc              ‹   ó^   K  — d|› �}|r|› d|› �}|                       |||¬¦  «        ƒ d{V —†S )a  
        Send a local image file natively via the platform API.

        Unlike send_image() which takes a URL, this takes a local file path.
        Override in subclasses for native photo attachments.
        Default falls back to sending the file path as text.
        u   ðŸ–¼ï¸� Image: r�  r&  Nr'  )r…  rÕ  rV  r$  r  rK  r7   s          r   Úsend_image_filez#BasePlatformAdapter.send_image_file¡  sZ   è è € ð . Ð-Ð-ˆØð 	(ØÐ'Ð' Ð'Ð'ˆDØ—Y’Y w¸Àx�YÑPÔPÐPÐPÐPÐPÐPÐPÐPr   c                 ó¾  — g }| }d| v }|                      dd¦  «        }t          j        d¦  «        }|                     | ¦  «        D ]Ò}|                     d¦  «                             ¦   «         }t          |¦  «        dk    r8|d         |d         k    r&|d         dv r|d	d…                              ¦   «         }|                     d¦  «                             d
¦  «        }|r4| 	                    t          j                             |¦  «        |f¦  «         ŒÓ|r>|                     d|¦  «        }t          j        dd|¦  «                             ¦   «         }||fS )a–  
        Extract MEDIA:<path> tags and [[audio_as_voice]] directives from response text.
        
        The TTS tool returns responses like:
            [[audio_as_voice]]
            MEDIA:/path/to/audio.ogg
        
        Args:
            content: The response text to scan.
        
        Returns:
            Tuple of (list of (path, is_voice) pairs, cleaned content with tags removed).
        ú[[audio_as_voice]]rX   zé[`"']?MEDIA:\s*(?P<path>`[^`\n]+`|"[^"\n]+"|'[^'\n]+'|(?:~/|/)\S+(?:[^\S\n]+\S+)*?\.(?:png|jpe?g|gif|webp|mp4|mov|avi|mkv|webm|ogg|opus|mp3|wav|m4a|epub|pdf|zip|rar|7z|docx?|xlsx?|pptx?|txt|csv|apk|ipa)(?=[\s`"',;:)\]}]|$)|\S+)[`"']?rÊ   r
   r   rÅ   z`"'r   z
`"',.;:)}]r=  r>  )rì   r?  Úcompiler@  r9  rH   r   Úlstripr`   rA  ru   rÊ   Ú
expanduserrB  )r  ÚmediarD  Úhas_voice_tagÚmedia_patternr:  rÊ   s          r   Úextract_mediaz!BasePlatformAdapter.extract_mediaµ  sf  € ð ˆØˆð -°Ð7ˆØ—/’/Ð"6¸Ñ;Ô;ˆõ œ
ð }ñ
ô 
ˆð #×+Ò+¨GÑ4Ô4ð 	Hð 	HˆEØ—;’;˜vÑ&Ô&×,Ò,Ñ.Ô.ˆDÝ�4‰yŒy˜AŠ~ˆ~ $ q¤'¨T°"¬XÒ"5Ð"5¸$¸q¼'ÀVÐ:KÐ:KØ˜A˜b˜D”z×'Ò'Ñ)Ô)�Ø—;’;˜vÑ&Ô&×-Ò-¨mÑ<Ô<ˆDØð HØ—’�bœg×0Ò0°Ñ6Ô6¸ÐFÑGÔGÐGøð ð 	AØ#×'Ò'¨¨GÑ4Ô4ˆGÝ”f˜Y¨°Ñ8Ô8×>Ò>Ñ@Ô@ˆGà�gˆ~Ðr   c                 ó¦  ‡— d}d                      d„ |D ¦   «         ¦  «        }t          j        d|z   dz   t          j        ¦  «        }g Št          j        d| t          j        ¦  «        D ]=}‰                     |                     ¦   «         |                     ¦   «         f¦  «         Œ>t          j        d| ¦  «        D ]=}‰                     |                     ¦   «         |                     ¦   «         f¦  «         Œ>dt          d	t          fˆfd
„}g }|                     | ¦  «        D ]Š} ||                     ¦   «         ¦  «        rŒ |                     d¦  «        }t          j                             |¦  «        }	t          j                             |	¦  «        r|                     ||	f¦  «         Œ‹t!          ¦   «         }
g }|D ]5\  }}	|	|
vr,|
                     |	¦  «         |                     ||	f¦  «         Œ6d„ |D ¦   «         }| }|rF|D ]\  }}|                     |d¦  «        }Œt          j        dd|¦  «                             ¦   «         }||fS )aW  
        Detect bare local file paths in response text for native media delivery.

        Matches absolute paths (/...) and tilde paths (~/) ending in common
        image or video extensions.  Validates each candidate with
        ``os.path.isfile()`` to avoid false positives from URLs or
        non-existent paths.

        Paths inside fenced code blocks (``` ... ```) and inline code
        (`...`) are ignored so that code samples are never mutilated.

        Returns:
            Tuple of (list of expanded file paths, cleaned text with the
            raw path strings removed).
        )
r2  ré   r3  r.  r4  r;  r<  r?  r>  r=  Ú|c              3   ó@   K  — | ]}|                      d ¦  «        V — ŒdS )rZ   N)r\  )rq   Úes     r   rs   z:BasePlatformAdapter.extract_local_files.<locals>.<genexpr>ô  s,   è è € ÐEÐE¨a˜AŸHšH S™MœMÐEÐEÐEÐEÐEÐEr   z/(?<![/:\w.])(?:~/|/)(?:[\w.\-]+/)*[\w.\-]+\.(?:z)\bz```[^\n]*\n.*?```z	`[^`\n]+`Úposr   c                 ó<   •‡ — t          ˆ fd„‰D ¦   «         ¦  «        S )Nc              3   ó>   •K  — | ]\  }}|‰cxk    o|k     nc V — Œd S ro   rß   )rq   r   re  rf  s      €r   rs   zLBasePlatformAdapter.extract_local_files.<locals>._in_code.<locals>.<genexpr>  s;   øè è € Ð;Ð;©¨¨1�q˜C�|�|’|�| !’|�|�|�|Ð;Ð;Ð;Ð;Ð;Ð;r   )r�   )rf  Ú
code_spanss   `€r   Ú_in_codez9BasePlatformAdapter.extract_local_files.<locals>._in_code  s'   øø€ ÝÐ;Ð;Ð;Ð;°
Ð;Ñ;Ô;Ñ;Ô;Ð;r   r   c                 ó   — g | ]\  }}|‘ŒS rß   rß   )rq   rO   Úexpandeds      r   ú
<listcomp>z;BasePlatformAdapter.extract_local_files.<locals>.<listcomp>  s   € Ð4Ð4Ð4™k˜a �Ð4Ð4Ð4r   rX   r=  r>  )Újoinr?  r[  Ú
IGNORECASEr@  ÚDOTALLrA  ÚstartÚendrc   r‘  r9  ru   rÊ   r]  ÚisfilerÂ  Úaddrì   rB  rH   )r  Ú_LOCAL_MEDIA_EXTSÚext_partÚpath_reÚmrj  Úfoundr:  rf   rl  ÚseenÚuniqueÚpathsrD  Ú_expri  s                  @r   Úextract_local_filesz'BasePlatformAdapter.extract_local_filesß  st  ø€ ð"
Ðð —8’8ÐEÐEÐ3DÐEÑEÔEÑEÔEˆõ
 ”*Ø>ÀÑIÈFÑRÝŒMñ
ô 
ˆð ˆ
Ý”Ð1°7½B¼IÑFÔFð 	4ð 	4ˆAØ×Ò˜qŸwšw™yœy¨!¯%ª%©'¬'Ð2Ñ3Ô3Ð3Ð3Ý”˜\¨7Ñ3Ô3ð 	4ð 	4ˆAØ×Ò˜qŸwšw™yœy¨!¯%ª%©'¬'Ð2Ñ3Ô3Ð3Ð3ð	<�#ð 	<¥$ð 	<ð 	<ð 	<ð 	<ð 	<ð 	<ð ˆØ×%Ò% gÑ.Ô.ð 	.ð 	.ˆEØˆx˜Ÿš™œÑ&Ô&ð ØØ—+’+˜a‘.”.ˆCÝ”w×)Ò)¨#Ñ.Ô.ˆHÝŒw�~Š~˜hÑ'Ô'ð .Ø—’˜c 8˜_Ñ-Ô-Ð-øõ ‘E”EˆØˆØ"ð 	/ð 	/‰MˆC�Ø˜tÐ#Ð#Ø—’˜Ñ"Ô"Ð"Ø—’˜s H˜oÑ.Ô.Ð.øà4Ð4¨VÐ4Ñ4Ô4ˆàˆØð 	AØ#ð 3ð 3‘	��TØ!Ÿ/š/¨#¨rÑ2Ô2��Ý”f˜Y¨°Ñ8Ô8×>Ò>Ñ@Ô@ˆGà�gˆ~Ðr   ç       @ÚintervalÚ
stop_eventc              ƒ   óD  K  — 	 	 |�n|                      ¦   «         rZ	 t          | d¦  «        r-	 |                      |¦  «        ƒ d{V —† n# t          $ r Y nw xY w| j                             |¦  «         dS || j        vr|                      ||¬¦  «        ƒ d{V —† |€t          j        |¦  «        ƒ d{V —† Œ´	 t          j	        | 
                    ¦   «         |¬¦  «        ƒ d{V —† n# t          j        $ r Y Œõw xY w	 t          | d¦  «        r-	 |                      |¦  «        ƒ d{V —† n# t          $ r Y nw xY w| j                             |¦  «         dS # t          j        $ r Y nw xY w	 t          | d¦  «        r-	 |                      |¦  «        ƒ d{V —† n# t          $ r Y nw xY w| j                             |¦  «         dS # t          | d¦  «        r-	 |                      |¦  «        ƒ d{V —† n# t          $ r Y nw xY w| j                             |¦  «         w xY w)u/  
        Continuously send typing indicator until cancelled.
        
        Telegram/Discord typing status expires after ~5 seconds, so we refresh every 2
        to recover quickly after progress messages interrupt it.
        
        Skips send_typing when the chat is in ``_typing_paused`` (e.g. while
        the agent is waiting for dangerous-command approval).  This is critical
        for Slack's Assistant API where ``assistant_threads_setStatus`` disables
        the compose box â€” pausing lets the user type ``/approve`` or ``/deny``.
        TNr"  rû  )r6   )Úis_setÚhasattrr"  rF   rÊ  Údiscardr   r  r  Úwait_forr  ÚTimeoutErrorÚCancelledError)r…  rÕ  r€  rü  r�  s        r   Ú_keep_typingz BasePlatformAdapter._keep_typing#  s  è è € ð$	1ðØÐ)¨j×.?Ò.?Ñ.AÔ.AÐ)Øõ$ �t˜]Ñ+Ô+ð ðØ×*Ò*¨7Ñ3Ô3Ð3Ð3Ð3Ð3Ð3Ð3Ð3Ð3øÝ ð ð ð Ø�DðøøøàÔ×'Ò'¨Ñ0Ô0Ð0Ð0Ð0ð-  $Ô"5Ð5Ð5Ø×*Ò*¨7¸XÐ*ÑFÔFÐFÐFÐFÐFÐFÐFÐFØÐ%Ý!œ-¨Ñ1Ô1Ð1Ð1Ð1Ð1Ð1Ð1Ð1ØðÝ!Ô*¨:¯?ª?Ñ+<Ô+<ÀhÐOÑOÔOÐOÐOÐOÐOÐOÐOÐOÐOøÝÔ+ð ð ð Ø�Hðøøøàõ �t˜]Ñ+Ô+ð ðØ×*Ò*¨7Ñ3Ô3Ð3Ð3Ð3Ð3Ð3Ð3Ð3Ð3øÝ ð ð ð Ø�DðøøøàÔ×'Ò'¨Ñ0Ô0Ð0Ð0Ð0øõ Ô%ð 	ð 	ð 	ØˆDð	øøøØõ �t˜]Ñ+Ô+ð ðØ×*Ò*¨7Ñ3Ô3Ð3Ð3Ð3Ð3Ð3Ð3Ð3Ð3øÝ ð ð ð Ø�DðøøøàÔ×'Ò'¨Ñ0Ô0Ð0Ð0Ð0øõ �t˜]Ñ+Ô+ð ðØ×*Ò*¨7Ñ3Ô3Ð3Ð3Ð3Ð3Ð3Ð3Ð3Ð3øÝ ð ð ð Ø�DðøøøàÔ×'Ò'¨Ñ0Ô0Ð0Ð0øøøs§   „E ­A	 Á	
AÁAÁ5AE Â9.C( Ã'E Ã(C:Ã7E Ã9C:Ã:E ÄD+ Ä+
D8Ä7D8ÅE)Å&G Å(E)Å)G Å>F Æ
F'Æ&F'ÇHÇG4Ç3HÇ4
HÇ>HÈ HÈHc                 ó:   — | j                              |¦  «         dS )uÕ   Pause typing indicator for a chat (e.g. during approval waits).

        Thread-safe (CPython GIL) â€” can be called from the sync agent thread
        while ``_keep_typing`` runs on the async event loop.
        N)rÊ  rt  r×  s     r   Úpause_typing_for_chatz)BasePlatformAdapter.pause_typing_for_chatQ  s!   € ð 	Ô×Ò Ñ(Ô(Ð(Ð(Ð(r   c                 ó:   — | j                              |¦  «         dS )z;Resume typing indicator for a chat after approval resolves.N)rÊ  r…  r×  s     r   Úresume_typing_for_chatz*BasePlatformAdapter.resume_typing_for_chatY  s   € àÔ×#Ò# GÑ,Ô,Ð,Ð,Ð,r   rš  c              ƒ   óÈ   K  — |r0| j                              |¦  «        }|�|                     ¦   «          	 |                      |¦  «        ƒ d{V —† dS # t          $ r Y dS w xY w)zDSignal the active session loop to stop and clear typing immediately.N)r¿  rJ   rÂ  r"  rF   )r…  rš  rÕ  Úinterrupt_events       r   Úinterrupt_session_activityz.BasePlatformAdapter.interrupt_session_activity]  s�   è è € àð 	&Ø"Ô3×7Ò7¸ÑDÔDˆOØÐ*Ø×#Ò#Ñ%Ô%Ð%ð	Ø×"Ò" 7Ñ+Ô+Ð+Ð+Ð+Ð+Ð+Ð+Ð+Ð+Ð+øÝð 	ð 	ð 	ØˆDˆDð	øøøs   ¶A Á
A!Á A!©Ú
generationÚcallbackr’  c                óz   — |rt          |¦  «        sdS |€|| j        |<   dS t          |¦  «        |f| j        |<   dS )zæRegister a deferred callback to fire after the main response.

        ``generation`` lets callers tie the callback to a specific gateway run
        generation so stale runs cannot clear callbacks owned by a fresher run.
        N)ÚcallablerÄ  rc   )r…  rš  r“  r’  s       r   Úregister_post_delivery_callbackz3BasePlatformAdapter.register_post_delivery_callbackh  sW   € ð ð 	¥(¨8Ñ"4Ô"4ð 	ØˆFØÐØ9AˆDÔ)¨+Ñ6Ð6Ð6å:=¸j¹/¼/È8Ð9TˆDÔ)¨+Ñ6Ð6Ð6r   c                ó¨  — |sdS | j                              |¦  «        }|€dS t          |t          ¦  «        rjt	          |¦  «        dk    rW|\  }}|�"t          |¦  «        t          |¦  «        k    rdS | j                              |d¦  «         t          |¦  «        r|ndS |�dS | j                              |d¦  «         t          |¦  «        r|ndS )zCPop a deferred callback, optionally requiring generation ownership.Nr
   )rÄ  rJ   r‹   Útupler   rc   Úpopr•  )r…  rš  r’  r{   Úentry_generationr“  s         r   Úpop_post_delivery_callbackz.BasePlatformAdapter.pop_post_delivery_callback{  sé   € ð ð 	Ø�4ØÔ-×1Ò1°+Ñ>Ô>ˆØˆ=Ø�4Ý�e�UÑ#Ô#ð 	<­¨E©
¬
°aª¨Ø).Ñ&Ð˜hØÐ%­#Ð.>Ñ*?Ô*?Å3ÀzÁ?Ä?Ò*RÐ*RØ�tØÔ)×-Ò-¨k¸4Ñ@Ô@Ð@Ý'¨Ñ1Ô1Ð;�8�8°tÐ;ØÐ!Ø�4ØÔ%×)Ò)¨+°tÑ<Ô<Ð<Ý  ™œÐ1ˆuˆu¨TÐ1r   r›  c              ƒ   ó
   K  — dS )z.Hook called when background processing begins.Nrß   )r…  r›  s     r   Úon_processing_startz'BasePlatformAdapter.on_processing_start–  ó
   è è € € € r   Úoutcomec              ƒ   ó
   K  — dS )z1Hook called when background processing completes.Nrß   )r…  r›  rŸ  s      r   Úon_processing_completez*BasePlatformAdapter.on_processing_complete™  rž  r   Ú	hook_namerŽ  rK  c              �   óà   K  — t          | |d¦  «        }t          |¦  «        sdS 	  ||i |¤Žƒ d{V —† dS # t          $ r-}t                               d| j        ||¦  «         Y d}~dS d}~ww xY w)zARun a lifecycle hook without letting failures break message flow.Nz[%s] %s hook failed: %s)r"   r•  rF   r©   rª   rN  )r…  r¢  rŽ  rK  Úhookre  s         r   Ú_run_processing_hookz(BasePlatformAdapter._run_processing_hookœ  s­   è è € å�t˜Y¨Ñ-Ô-ˆÝ˜‰~Œ~ð 	ØˆFð	OØ�$˜Ð' Ð'Ð'Ð'Ð'Ð'Ð'Ð'Ð'Ð'Ð'Ð'øÝð 	Oð 	Oð 	OÝ�NŠNÐ4°d´iÀÈAÑNÔNÐNÐNÐNÐNÐNÐNÐNøøøøð	Oøøøs   ¦6 ¶
A-Á "A(Á(A-r•  c                 ót   ‡— | sdS |                       ¦   «         Št          ˆfd„t          D ¦   «         ¦  «        S )zGReturn True if the error string looks like a transient network failure.Fc              3   ó    •K  — | ]}|‰v V — Œ	d S ro   rß   )rq   ÚpatÚlowereds     €r   rs   z:BasePlatformAdapter._is_retryable_error.<locals>.<genexpr>¬  s'   øè è € ÐGÐG c�3˜'�>ÐGÐGÐGÐGÐGÐGr   )r_   r�   Ú_RETRYABLE_ERROR_PATTERNS©r•  r©  s    @r   Ú_is_retryable_errorz'BasePlatformAdapter._is_retryable_error¦  sC   ø€ ð ð 	Ø�5Ø—+’+‘-”-ˆÝÐGÐGÐGÐGÕ-FÐGÑGÔGÑGÔGÐGr   c                 óJ   — | sdS |                       ¦   «         }d|v pd|v pd|v S )uÖ   Return True if the error string indicates a read/write timeout.

        Timeout errors are NOT retryable and should NOT trigger plain-text
        fallback â€” the request may have already been delivered.
        Fz	timed outÚreadtimeoutÚwritetimeout)r_   r«  s     r   Ú_is_timeout_errorz%BasePlatformAdapter._is_timeout_error®  s>   € ð ð 	Ø�5Ø—+’+‘-”-ˆØ˜gÐ%Ð^¨¸'Ð)AÐ^À^ÐW^ÐE^Ð^r   r
   Úmax_retriesÚ
base_delayr”  c           	   ƒ   ó˜  K  — |                       ||||¬¦  «        ƒ d{V —†}|j        r|S |j        pd}|j        p|                      |¦  «        }	|	s|                      |¦  «        r|S |	�rft          d|dz   ¦  «        D ]Ó}
|d|
dz
  z  z  t          j        dd¦  «        z   }t           
                    d| j        |
|||¦  «         t          j        |¦  «        ƒ d{V —† |                       ||||¬¦  «        ƒ d{V —†}|j        r%t                               d| j        |
¦  «         |c S |j        pd}|j        s|                      |¦  «        s n€ŒÔt                               d	| j        ||¦  «         d
}	 |                       ||||¬¦  «        ƒ d{V —† n8# t          $ r+}t                               d| j        |¦  «         Y d}~nd}~ww xY w|S t           
                    d| j        |¦  «         |                       |d|dd…         › �||¬¦  «        ƒ d{V —†}|j        s&t                               d| j        |j        ¦  «         |S )ax  
        Send a message with automatic retry for transient network errors.

        On permanent failures (e.g. formatting / permission errors) falls back
        to a plain-text version before giving up. If all attempts fail due to
        network errors, sends the user a brief delivery-failure notice so they
        know to retry rather than waiting indefinitely.
        ©rÕ  r  r  rü  NrX   r   r
   r   z7[%s] Send failed (attempt %d/%d, retrying in %.1fs): %sz[%s] Send succeeded on retry %dz4[%s] Failed to deliver response after %d retries: %su‹   âš ï¸� Message delivery failed after multiple attempts. Please try again â€” your request was processed but the response could not be sent.z/[%s] Could not send delivery-failure notice: %su3   [%s] Send failed: %s â€” trying plain-text fallbackz+(Response formatting failed, plain text:)

i¬  z"[%s] Fallback send also failed: %s)r  rm  r•  r—  r¬  r°  r  ÚrandomÚuniformr©   rª   rN  r  r  ÚinforF   r  )r…  rÕ  r  r  rü  r±  r²  rô  Ú	error_strÚ
is_networkr  ÚdelayÚnoticeÚ
notify_errÚfallback_results                  r   Ú_send_with_retryz$BasePlatformAdapter._send_with_retryº  s.  è è € ð$ —y’yØØØØð	 !ñ 
ô 
ð 
ð 
ð 
ð 
ð 
ð 
ˆð Œ>ð 	ØˆMà”LÐ& Bˆ	ØÔ%ÐL¨×)AÒ)AÀ)Ñ)LÔ)Lˆ
ð ð 	˜d×4Ò4°YÑ?Ô?ð 	ØˆMàñ  	å   K°!¡OÑ4Ô4ð ð �Ø" a¨G°a©KÑ&8Ñ9½F¼NÈ1ÈaÑ<PÔ<PÑP�Ý—’ØMØ”I˜w¨°U¸Iñô ð õ ”m EÑ*Ô*Ð*Ð*Ð*Ð*Ð*Ð*Ð*Ø#ŸyšyØ#Ø#Ø%Ø%ð	  )ñ  ô  ð ð ð ð ð ð �ð ”>ð "Ý—K’KÐ AÀ4Ä9ÈgÑVÔVÐVØ!�M�M�MØ"œLÐ.¨B�	ØÔ(ð ¨D×,DÒ,DÀYÑ,OÔ,Oð Ø�Eøõ —’ÐSÐUYÔU^Ð`kÐmvÑwÔwÐwðmð ðkØŸ)š)¨G¸VÈhÐai˜)ÑjÔjÐjÐjÐjÐjÐjÐjÐjÐjøÝ ð kð kð kÝ—L’LÐ!RÐTXÔT]Ð_iÑjÔjÐjÐjÐjÐjÐjÐjøøøøðkøøøà�õ 	�ŠÐLÈdÌiÐYbÑcÔcÐcØ $§	¢	ØØTÀGÈEÈTÈEÄNÐTÐTØØð	 !*ñ !
ô !
ð 
ð 
ð 
ð 
ð 
ð 
ˆð Ô&ð 	aÝ�LŠLÐ=¸t¼yÈ/ÔJ_Ñ`Ô`Ð`ØÐs   Å8F Æ
GÆ"!GÇGÚexisting_textÚnew_textc                 óª   — | s|S d„ |                       d¦  «        D ¦   «         }|                     ¦   «         |vr| › d|› �                     ¦   «         S | S )a`  Merge a new caption into existing text, avoiding duplicates.

        Uses line-by-line exact match (not substring) to prevent false positives
        where a shorter caption is silently dropped because it appears as a
        substring of a longer one (e.g. "Meeting" inside "Meeting agenda").
        Whitespace is normalised for comparison.
        c                 ó6   — g | ]}|                      ¦   «         ‘ŒS rß   rp   )rq   Úcs     r   rm  z6BasePlatformAdapter._merge_caption.<locals>.<listcomp>  s    € ÐLÐLÐL¨1˜QŸWšW™YœYÐLÐLÐLr   r>  )rx   rH   )r¿  rÀ  Úexisting_captionss      r   rŸ  z"BasePlatformAdapter._merge_caption  so   € ð ð 	ØˆOØLÐL°×0CÒ0CÀFÑ0KÔ0KÐLÑLÔLÐØ�>Š>ÑÔÐ#4Ð4Ð4Ø#Ð3Ð3¨Ð3Ð3×9Ò9Ñ;Ô;Ð;ØÐr   ©ÚguardrÆ  c                ób   — | j                              |¦  «        }|€dS |�||urdS | j         |= dS )ac  Release the adapter-level guard for a session.

        When ``guard`` is provided, only release the entry if it still points
        at that exact Event.  This lets reset-like commands swap in a temporary
        guard while the old processing task unwinds, without having the old
        task's cleanup accidentally clear the replacement guard.
        N)r¿  rJ   )r…  rš  rÆ  Úcurrent_guards       r   Ú_release_session_guardz*BasePlatformAdapter._release_session_guard$  sK   € ð Ô-×1Ò1°+Ñ>Ô>ˆØÐ ØˆFØÐ °eÐ!;Ð!;ØˆFØÔ! +Ð.Ð.Ð.r   c                 ó’   — | j                              |¦  «        }|€dS t          |dd¦  «        }t          |o	 |¦   «         ¦  «        S )uk  Return True if the owner task for ``session_key`` is done/cancelled.

        A lock is "stale" when the adapter still has ``_active_sessions[key]``
        AND a known owner task in ``_session_tasks`` that has already exited.
        When there is no owner task at all, that usually means the guard was
        installed by some path other than handle_message() (tests sometimes
        install guards directly) â€” don't treat that as stale.  The on-entry
        self-heal only needs to handle the production split-brain case where
        an owner task was recorded, then exited without clearing its guard.
        NFÚdone)rÁ  rJ   r"   r‘  )r…  rš  ÚtaskrË  s       r   Ú_session_task_is_stalez*BasePlatformAdapter._session_task_is_stale8  sM   € ð Ô"×&Ò& {Ñ3Ô3ˆØˆ<Ø�5Ý�t˜V TÑ*Ô*ˆÝ�D�O˜T˜T™VœVÑ$Ô$Ð$r   c                 ó.  — || j         vrdS |                      |¦  «        sdS t                               d| j        |¦  «         | j                              |d¦  «         | j                             |d¦  «         | j                             |d¦  «         dS )u  Clear a stale session lock if the owner task is already gone.

        Returns True if a stale lock was healed.  Returns False if there is
        no lock, or the owner task is still alive (the normal busy case).

        This is the on-entry safety net sidbin's issue #11016 analysis calls
        for: without it, a split-brain â€” adapter still thinks the session is
        active, but nothing is actually processing â€” traps the chat in
        infinite "Interrupting current task..." until the gateway is
        restarted.
        FzB[%s] Healing stale session lock for %s (owner task is done/absent)NT)r¿  rÍ  r©   rª   rN  r™  rÀ  rÁ  ©r…  rš  s     r   Ú_heal_stale_session_lockz,BasePlatformAdapter._heal_stale_session_lockI  s¥   € ð ˜dÔ3Ð3Ð3Ø�5Ø×*Ò*¨;Ñ7Ô7ð 	Ø�5Ý�ŠØPØŒIØñ	
ô 	
ð 	
ð
 	Ô×!Ò! +¨tÑ4Ô4Ð4ØÔ×"Ò" ;°Ñ5Ô5Ð5ØÔ×Ò ¨TÑ2Ô2Ð2Øˆtr   )r�  r�  c                ó  — |pt          j        ¦   «         }|| j        |<   t          j        |                      ||¦  «        ¦  «        }|| j        |<   	 | j                             |¦  «         nC# t          $ r6 | j         	                    |d¦  «         |  
                    ||¬¦  «         Y dS w xY wt          |d¦  «        r>|                     | j        j        ¦  «         |                     | j        j        ¦  «         dS )aH  Spawn a background processing task under the given session guard.

        Returns True on success.  If the runtime stubs ``create_task`` with a
        non-Task sentinel (some tests do this), the guard is rolled back and
        False is returned so the caller isn't left holding a half-installed
        session lock.
        NrÅ  FÚadd_done_callbackT)r  ÚEventr¿  Úcreate_taskÚ_process_message_backgroundrÁ  rÃ  rt  Ú	TypeErrorr™  rÉ  r„  rÒ  r…  rÅ  )r…  r›  rš  r�  rÆ  rÌ  s         r   Ú_start_session_processingz-BasePlatformAdapter._start_session_processingc  s  € ð  Ð2¥7¤=¡?¤?ˆØ-2ˆÔ˜kÑ*åÔ" 4×#CÒ#CÀEÈ;Ñ#WÔ#WÑXÔXˆØ+/ˆÔ˜KÑ(ð	ØÔ"×&Ò& tÑ,Ô,Ð,Ð,øÝð 	ð 	ð 	ð Ô×#Ò# K°Ñ6Ô6Ð6Ø×'Ò'¨¸5Ð'ÑAÔAÐAØ�5�5ð	øøøõ �4Ð,Ñ-Ô-ð 	KØ×"Ò" 4Ô#9Ô#AÑBÔBÐBØ×"Ò" 4Ô#AÔ#IÑJÔJÐJØˆts   ÁA. Á.<B.Â-B.T©Úrelease_guardÚdiscard_pendingrÙ  rÚ  c             ƒ   ó  K  — | j                              |d¦  «        }|�°|                     ¦   «         sœt                               d| j        |¦  «         | j                             |¦  «         |                     ¦   «          	 |ƒ d{V —† nC# t          j
        $ r Y n2t          $ r& t                               d| j        |d¬¦  «         Y nw xY w|r| j                             |d¦  «         |r|                      |¦  «         dS dS )a  Cancel in-flight processing for a single session.

        ``release_guard=False`` keeps the adapter-level session guard in place
        so reset-like commands can finish atomically before follow-up messages
        are allowed to start a fresh background task.
        Nz0[%s] Cancelling active processing for session %sz3[%s] Session cancellation raised while unwinding %sT©Úexc_info)rÁ  r™  rË  r©   r  rN  rÅ  rt  Úcancelr  rˆ  rF   rÀ  rÉ  )r…  rš  rÙ  rÚ  rÌ  s        r   Úcancel_session_processingz-BasePlatformAdapter.cancel_session_processingƒ  sI  è è € ð Ô"×&Ò& {°DÑ9Ô9ˆØÐ D§I¢I¡K¤KÐÝ�LŠLØBØ”	Øñô ð ð
 Ô*×.Ò.¨tÑ4Ô4Ð4Ø�KŠK‰MŒMˆMð
Ø�
�
�
�
�
�
�
�
øÝÔ)ð ð ð Ø�Ýð ð ð Ý—’ØIØ”IØØ!ð	 ñ ô ð ð ð ðøøøð ð 	:ØÔ"×&Ò& {°DÑ9Ô9Ð9Øð 	5Ø×'Ò'¨Ñ4Ô4Ð4Ð4Ð4ð	5ð 	5s   ÂB ÂCÂ,CÃCÚcommand_guardc              ƒ   ó¢   K  — | j                              |d¦  «        }|                      ||¬¦  «         |€dS |                      ||¦  «         dS )u2  Resume the latest queued follow-up once a session command completes.

        Called at the tail of /stop, /new, and /reset dispatch.  Releases the
        command-scoped guard, then â€” if a follow-up message landed while the
        command was running â€” spawns a fresh processing task for it.
        NrÅ  )rÀ  r™  rÉ  r×  )r…  rš  rà  Úpending_events       r   Ú$_drain_pending_after_session_commandz8BasePlatformAdapter._drain_pending_after_session_command©  s`   è è € ð Ô.×2Ò2°;ÀÑEÔEˆØ×#Ò# K°}Ð#ÑEÔEÐEØÐ ØˆFØ×&Ò& }°kÑBÔBÐBÐBÐBr   Úcmdc              ƒ   ó²  K  — t                                d| j        ||¦  «         | j                             |¦  «        }t          j        ¦   «         }|| j        |<   |j        j        rd|j        j        ind}	 |  	                    |¦  «        ƒ d{V —†}|  
                    |dd¬¦  «        ƒ d{V —† |r.|                      |j        j        ||j        |¬¦  «        ƒ d{V —† nX# t          $ rK | j                             |¦  «        |u r-|| j        v r|�|| j        |<   n|                      ||¬¦  «         ‚ w xY w|                      ||¦  «        ƒ d{V —† dS )a�  Dispatch a reset-like bypass command while preserving guard ordering.

        /stop, /new, and /reset must:
          1. Keep the session guard installed while the runner processes the
             command (so a racing follow-up message stays queued, not
             dispatched as a second parallel run).
          2. Cancel the old in-flight adapter task only AFTER the runner has
             finished handling the command (so the runner sees consistent
             state and its response is sent in order).
          3. Release the command-scoped guard and drain the latest queued
             follow-up exactly once, after 1 and 2 complete.
        ú8[%s] Command '/%s' bypassing active-session guard for %sÚ	thread_idNFrØ  r´  rÅ  )r©   r  rN  r¿  rJ   r  rÓ  rv  rç  r¹  rß  r¾  rÕ  rx  rF   rÁ  rÉ  rã  )r…  r›  rš  rä  rÈ  rà  Úthread_metarÕ   s           r   Ú _dispatch_active_session_commandz4BasePlatformAdapter._dispatch_active_session_commandº  sï  è è € õ$ 	�ŠØFØŒIØØñ		
ô 	
ð 	
ð Ô-×1Ò1°+Ñ>Ô>ˆÝœ™œˆØ-:ˆÔ˜kÑ*Ø?D¼|Ô?UÐ_�{ E¤LÔ$:Ð;Ð;Ð[_ˆð	Ø!×2Ò2°5Ñ9Ô9Ð9Ð9Ð9Ð9Ð9Ð9ˆHð ×0Ò0ØØ#Ø %ð 1ñ ô ð ð ð ð ð ð ð ð
 ð Ø×+Ò+Ø!œLÔ0Ø$Ø"Ô-Ø(ð	 ,ñ ô ð ð ð ð ð ð ð øøõ ð 	ð 	ð 	ð Ô$×(Ò(¨Ñ5Ô5¸ÐFÐFØ $Ô"5Ð5Ð5¸-Ð:SØ9F�DÔ)¨+Ñ6Ð6à×/Ò/°À=Ð/ÑQÔQÐQØð	øøøð ×7Ò7¸À]ÑSÔSÐSÐSÐSÐSÐSÐSÐSÐSÐSs   Á9A)C# Ã#AD8c              ƒ   óÐ  K  — | j         sdS t          |j        | j        j                             dd¦  «        | j        j                             dd¦  «        ¬¦  «        }|| j        v r|                      |¦  «         || j        v �rI|                     ¦   «         }ddl	m
}  ||¦  «        �r(|d	v r\	 |                      |||¦  «        ƒ d{V —† n;# t          $ r.}t                               d
| j        ||d¬¦  «         Y d}~nd}~ww xY wdS t                               d| j        ||¦  «         	 |j        j        rd|j        j        ind}|                       |¦  «        ƒ d{V —†}|r.|                      |j        j        ||j        |¬¦  «        ƒ d{V —† n;# t          $ r.}t                               d
| j        ||d¬¦  «         Y d}~nd}~ww xY wdS | j        �Z	 |                      ||¦  «        ƒ d{V —†rdS n:# t          $ r-}t                               d| j        |d¬¦  «         Y d}~nd}~ww xY w|j        t.          j        k    r9t                               d| j        |¦  «         t3          | j        ||¦  «         dS t                               d| j        |¦  «         || j        |<   | j        |                              ¦   «          dS |                      ||¦  «         dS )zñ
        Process an incoming message.
        
        This method returns quickly by spawning background tasks.
        This allows new messages to be processed even while an agent is running,
        enabling interruption support.
        NÚgroup_sessions_per_userTÚthread_sessions_per_userF)rë  rì  r   )Úshould_bypass_active_session)ÚstopÚnewÚresetz&[%s] Command '/%s' dispatch failed: %srÜ  ræ  rç  r´  z$[%s] Busy-session handler failed: %sz=[%s] Queuing photo follow-up for session %s without interruptuD   [%s] New message while session %s is active â€” triggering interrupt)r¹  r¾   rv  r·  ÚextrarJ   r¿  rÐ  r‹  Úhermes_cli.commandsrí  ré  rF   r©   r•  rN  r  rç  r¾  rÕ  rx  rÆ  ru  rW  rd  r¥  rÀ  rÂ  r×  )r…  r›  rš  rä  rí  re  Ú_thread_metarÕ   s           r   Úhandle_messagez"BasePlatformAdapter.handle_messageô  sá  è è € ð Ô$ð 	ØˆFå'ØŒLØ$(¤KÔ$5×$9Ò$9Ð:SÐUYÑ$ZÔ$ZØ%)¤[Ô%6×%:Ò%:Ð;UÐW\Ñ%]Ô%]ð
ñ 
ô 
ˆð ˜$Ô/Ð/Ð/Ø×)Ò)¨+Ñ6Ô6Ð6ð ˜$Ô/Ð/Ñ/ð ×#Ò#Ñ%Ô%ˆCØHÐHÐHÐHÐHÐHà+Ð+¨CÑ0Ô0ñ "ð
 Ð2Ð2Ð2ðØ"×CÒCÀEÈ;ÐX[Ñ\Ô\Ð\Ð\Ð\Ð\Ð\Ð\Ð\Ð\øÝ$ð ð ð ÝŸšØDØ œI s¨A¸ð %ñ ô ð ð ð ð ð ð øøøøðøøøð
 �Fõ
 —’ØNØ”I˜s Kñô ð ðmØLQÌLÔLbÐ#l K°´Ô1GÐ#HÐ#HÐhl�LØ%)×%:Ò%:¸5Ñ%AÔ%AÐAÐAÐAÐAÐAÐA�HØð Ø"×3Ò3Ø$)¤LÔ$8Ø$,Ø%*Ô%5Ø%1ð	 4ñ ô ð ð ð ð ð ð ð øøõ !ð mð mð mÝ—L’LÐ!IÈ4Ì9ÐVYÐ[\Ðgk�LÑlÔlÐlÐlÐlÐlÐlÐløøøøðmøøøà�àÔ)Ð5ðfØ!×7Ò7¸¸{ÑKÔKÐKÐKÐKÐKÐKÐKð Ø˜ðøå ð fð fð fÝ—L’LÐ!GÈÌÐTUÐ`d�LÑeÔeÐeÐeÐeÐeÐeÐeøøøøðføøøð Ô!¥[Ô%6Ò6Ð6Ý—’Ð\Ð^bÔ^gÐitÑuÔuÐuÝ+¨DÔ,BÀKÐQVÑWÔWÐWØ�õ �LŠLÐ_ÐaeÔajÐlwÑxÔxÐxØ27ˆDÔ" ;Ñ/àÔ! +Ô.×2Ò2Ñ4Ô4Ð4ØˆFð 	×&Ò& u¨kÑ:Ô:Ð:Ð:Ð:sI   Â2C Ã
DÃ$DÄDÄ0A'F Æ
GÆ"$GÇGÇG< Ç<
H3È#H.È.H3c                  ó4  — t          j        dd¦  «                             ¦   «         } | dk    rdS t          t          j        dd¦  «        ¦  «        }t          t          j        dd¦  «        ¦  «        }| dk    rd	\  }}t	          j        |d
z  |d
z  ¦  «        S )ac  
        Return a random delay in seconds for human-like response pacing.

        Reads from env vars:
          HERMES_HUMAN_DELAY_MODE: "off" (default) | "natural" | "custom"
          HERMES_HUMAN_DELAY_MIN_MS: minimum delay in ms (default 800, custom mode)
          HERMES_HUMAN_DELAY_MAX_MS: maximum delay in ms (default 2500, custom mode)
        ÚHERMES_HUMAN_DELAY_MODEÚoffg        ÚHERMES_HUMAN_DELAY_MIN_MSÚ800ÚHERMES_HUMAN_DELAY_MAX_MSÚ2500Únatural)i   iÄ	  g     @�@)ru   Úgetenvr_   rc   rµ  r¶  )ÚmodeÚmin_msÚmax_mss      r   Ú_get_human_delayz$BasePlatformAdapter._get_human_delay_  s’   € õ ŒyÐ2°EÑ:Ô:×@Ò@ÑBÔBˆØ�5Š=ˆ=Ø�3Ý•R”YÐ:¸EÑBÔBÑCÔCˆÝ•R”YÐ:¸FÑCÔCÑDÔDˆØ�9ÒÐØ&‰NˆF�FÝŒ~˜f v™o¨v¸©Ñ?Ô?Ð?r   c           	   ƒ   ój#  ‡3‡4K  — dŠ3dŠ4ˆ3ˆ4fd„}| j                              |¦  «        pt          j        ¦   «         }|| j         |<   t	          |dd¦  «        }|j        j        rd|j        j        ind}d|i}	 t          j        | j	        ¦  «        }n# t          t          f$ r d}Y nw xY w|�	d|j        v r||d<   t          j         | j	        |j        j        fi |¤Ž¦  «        }		 |                      d|¦  «        ƒ d{V —† |                      |¦  «        ƒ d{V —†}
|
r@|                     ¦   «         r,|| j        v r#t&                               d	| j        |¦  «         d}
|
s+t&                               d
| j        |j        j        ¦  «         |
�r$|                      |
¦  «        \  }}
|                      |
¦  «        \  }}|                     dd¦  «                             ¦   «         }t7          j        dd|¦  «                             ¦   «         }|r<t&                               d| j        t;          |¦  «        t;          |
¦  «        ¦  «         |                      |¦  «        \  }}|r.t&                               d| j        t;          |¦  «        ¦  «         d}|                      |j        j        ¦  «        rð|j         tB          j"        k    rÛ|rÙ|s×	 ddl#m$}m%}  |¦   «         r‹ddl&}t7          j        dd|¦  «        dd…                              ¦   «         }|st          d¦  «        ‚t          j'        ||¬¦  «        ƒ d{V —†}| (                    |¦  «        }|                     d¦  «        }n8# tR          $ r+}t&           *                    d| j        |¦  «         Y d}~nd}~ww xY w|rštW          |¦  «         ,                    ¦   «         ry	 |  -                    |j        j        ||¬¦  «        ƒ d{V —† 	 t]          j/        |¦  «         n:# t`          $ r Y n.w xY w# 	 t]          j/        |¦  «         w # t`          $ r Y w w xY wxY w|rrt&                               d| j        t;          |¦  «        |j        j        ¦  «         |  1                    |j        j        ||j2        |¬¦  «        ƒ d{V —†} ||¦  «         |  3                    ¦   «         }|r.t&                               d| j        t;          |¦  «        ¦  «         |D �]:\  }}|dk    rt          j4        |¦  «        ƒ d{V —† 	 t&                               d| j        tk          |¦  «        |r
|dd…         nd¦  «         |  6                    |¦  «        r.|  7                    |j        j        ||r|nd|¬¦  «        ƒ d{V —†}n-|  8                    |j        j        ||r|nd|¬¦  «        ƒ d{V —†}|j9        s&t&           :                    d | j        |j:        ¦  «         �Œ# tR          $ r.}t&           :                    d!| j        |d"¬#¦  «         Y d}~�Œ4d}~ww xY wh d$£}h d%£}h d&£}|D �]c\  } }!|dk    rt          j4        |¦  «        ƒ d{V —† 	 tW          | ¦  «        j;         <                    ¦   «         }"|"|v r)|  =                    |j        j        | |¬¦  «        ƒ d{V —†}#n‚|"|v r)|  >                    |j        j        | |¬'¦  «        ƒ d{V —†}#nU|"|v r)|  ?                    |j        j        | |¬(¦  «        ƒ d{V —†}#n(|  @                    |j        j        | |¬)¦  «        ƒ d{V —†}#|#j9        s't&           *                    d*| j        |"|#j:        ¦  «         �Œ,# tR          $ r,}$t&           *                    d+| j        |$¦  «         Y d}$~$�Œ]d}$~$ww xY w|D �]}%|dk    rt          j4        |¦  «        ƒ d{V —† 	 tW          |%¦  «        j;         <                    ¦   «         }"|"|v r)|  ?                    |j        j        |%|¬(¦  «        ƒ d{V —† nU|"|v r)|  >                    |j        j        |%|¬'¦  «        ƒ d{V —† n(|  @                    |j        j        |%|¬)¦  «        ƒ d{V —† ŒÍ# tR          $ r,}&t&           :                    d,| j        |%|&¦  «         Y d}&~&Œþd}&~&ww xY w‰3r‰4ntƒ          |
¦  «         }'|                      d-||'rt„          jC        nt„          jD        ¦  «        ƒ d{V —† || j        v �rì| j         E                    |¦  «        }(t&                               d.| j        ¦  «         | j                              |¦  «        })|)�|) F                    ¦   «          |	 G                    ¦   «          	 |	ƒ d{V —† n# t          jH        $ r Y nw xY w|  I                    |(|¦  «        ƒ d{V —† 	 |}*t•          | d/¦  «        r|  K                    ||*¬0¦  «        }+n%t	          | d1i ¦  «         E                    |d¦  «        }+t™          |+¦  «        r	  |+¦   «          n# tR          $ r Y nw xY w|	 G                    ¦   «          	 |	ƒ d{V —† n# t          jH        $ r Y nw xY w	 t•          | d2¦  «        r%|  M                    |j        j        ¦  «        ƒ d{V —† n# tR          $ r Y nw xY w| j         E                    |d¦  «        },|,�Ït&                               d3| j        ¦  «         | j                              |¦  «        })|)�|) F                    ¦   «          t          j        |  I                    |,|¦  «        ¦  «        }-|-| jN        |<   	 | jO         P                    |-¦  «         |- Q                    | jO        jR        ¦  «         dS # t          $ r Y dS w xY wt          jS        ¦   «         }.|.�$| jN                             |¦  «        |.u r| jN        |= |  T                    ||¬4¦  «         dS �n\# t          jH        $ rU t          jS        ¦   «         }.t„          jU        }/|.�	|.| jV        vrt„          jD        }/|                      d-||/¦  «        ƒ d{V —† ‚ tR          $ rí}0|                      d-|t„          jD        ¦  «        ƒ d{V —† t&           :                    d5| j        |0d"¬#¦  «         	 t¯          |0¦  «        jX        }1t³          |0¦  «        rt³          |0¦  «        dd6…         nd7}2|j        j        rd|j        j        ind}|  Z                    |j        j        d8|1› d9|2› d:�|¬;¦  «        ƒ d{V —† n# tR          $ r Y nw xY wY d}0~0nd}0~0ww xY w|}*t•          | d/¦  «        r|  K                    ||*¬0¦  «        }+n%t	          | d1i ¦  «         E                    |d¦  «        }+t™          |+¦  «        r	  |+¦   «          n# tR          $ r Y nw xY w|	 G                    ¦   «          	 |	ƒ d{V —† n# t          jH        $ r Y nw xY w	 t•          | d2¦  «        r%|  M                    |j        j        ¦  «        ƒ d{V —† n# tR          $ r Y nw xY w| j         E                    |d¦  «        },|,�Ït&                               d3| j        ¦  «         | j                              |¦  «        })|)�|) F                    ¦   «          t          j        |  I                    |,|¦  «        ¦  «        }-|-| jN        |<   	 | jO         P                    |-¦  «         |- Q                    | jO        jR        ¦  «         dS # t          $ r Y dS w xY wt          jS        ¦   «         }.|.�$| jN                             |¦  «        |.u r| jN        |= |  T                    ||¬4¦  «         dS # |}*t•          | d/¦  «        r|  K                    ||*¬0¦  «        }+n%t	          | d1i ¦  «         E                    |d¦  «        }+t™          |+¦  «        r	  |+¦   «          n# tR          $ r Y nw xY w|	 G                    ¦   «          	 |	ƒ d{V —† n# t          jH        $ r Y nw xY w	 t•          | d2¦  «        r%|  M                    |j        j        ¦  «        ƒ d{V —† n# tR          $ r Y nw xY w| j         E                    |d¦  «        },|,�Ít&                               d3| j        ¦  «         | j                              |¦  «        })|)�|) F                    ¦   «          t          j        |  I                    |,|¦  «        ¦  «        }-|-| jN        |<   	 | jO         P                    |-¦  «         |- Q                    | jO        jR        ¦  «         w # t          $ r Y w w xY wt          jS        ¦   «         }.|.�$| jN                             |¦  «        |.u r| jN        |= |  T                    ||¬4¦  «         w xY w)<z4Background task that actually processes the message.Fc                 ó>   •— | €d S dŠt          | dd¦  «        rdŠd S d S )NTrm  F)r"   )rô  Údelivery_attemptedÚdelivery_succeededs    €€r   Ú_record_deliveryzIBasePlatformAdapter._process_message_background.<locals>._record_deliveryx  s=   ø€ àˆ~Ø�Ø!%ÐÝ�v˜y¨%Ñ0Ô0ð *Ø%)Ð"Ð"Ð"ð*ð *r   Ú_hermes_run_generationNrç  rü  r�  r�  z:[%s] Suppressing stale response for interrupted session %sz0[%s] Handler returned empty/None response for %srZ  rX   zMEDIA:\s*\S+z<[%s] extract_images found %d image(s) in response (%d chars)z5[%s] extract_local_files found %d file(s) in responser   )Útext_to_speech_toolÚcheck_tts_requirementsz[*_`#\[\]()]i   z!Empty text after markdown cleanup)r7   rR  z[%s] Auto-TTS failed: %s)rÕ  rI  rü  z&[%s] Sending response (%d chars) to %sr´  z1[%s] Extracted %d image(s) to send as attachmentsz[%s] Sending image: %s (alt=%s)é   )rÕ  r)  r$  rü  )rÕ  r#  r$  rü  z[%s] Failed to send image: %sz[%s] Error sending image: %sTrÜ  >   ú.m4aú.mp3ú.wavú.opusr3  >   ú.3gpr?  r>  r<  r;  r=  >   r.  ré   r2  r3  r4  )rÕ  rO  rü  )rÕ  rV  rü  )rÕ  rR  rü  z"[%s] Failed to send media (%s): %sz[%s] Error sending media: %sz$[%s] Error sending local file %s: %sr¡  z-[%s] Processing queued message from interruptr›  r‘  rÄ  r"  uH   [%s] Late-arrival pending message during cleanup â€” spawning drain taskrÅ  z[%s] Error handling message: %si,  zno details availablezSorry, I encountered an error (z).
z2
Try again or use /reset to start a fresh session.)rÕ  r  rü  )[r¿  rJ   r  rÓ  r"   rv  rç  ÚinspectÚ	signaturer‰  rÖ  r#   Ú
parametersrÔ  rÕ  r¥  r¹  rƒ  rÀ  r©   r·  rN  r  ra  rH  rì   rH   r?  rB  r   r~  rØ  ru  rW  rg  Útools.tts_toolr  r	  ÚjsonÚ	to_threadÚloadsrF   rª   r²   ÚexistsrN  ru   Úremover)   r¾  rx  r  r  rÎ   r/  r+  r(  rm  r•  r†   r_   rL  rQ  rX  rU  r‘  rl  rp  rq  r™  ÚclearrÞ  rˆ  rÕ  r„  r›  r•  r"  rÁ  rÃ  rt  rÒ  r…  Úcurrent_taskrÉ  rr  rÅ  Útyper  r]   r  )5r…  r›  rš  r  r�  Úcallback_generationÚ_thread_metadataÚ_keep_typing_kwargsÚ_keep_typing_sigÚtyping_taskrÕ   Úmedia_filesrC  Útext_contentÚlocal_filesÚ	_tts_pathr  r	  Ú_jsonÚspeech_textÚtts_result_strÚtts_dataÚtts_errrô  Úhuman_delayr#  rF  Ú
img_resultÚimg_errÚ_AUDIO_EXTSÚ_VIDEO_EXTSÚ_IMAGE_EXTSÚ
media_pathÚis_voicerê   Úmedia_resultÚ	media_errrR  Úfile_errÚprocessing_okrâ  Ú_activeÚ_callback_generationÚ_post_cbÚlate_pendingÚ
drain_taskr  rŸ  re  Ú
error_typeÚerror_detailr  r  s5                                                      @@r   rÕ  z/BasePlatformAdapter._process_message_backgroundr  sg  øøè è € ð #ÐØ"Ðð	*ð 	*ð 	*ð 	*ð 	*ð 	*ð Ô/×3Ò3°KÑ@Ô@ÐSÅGÄMÁOÄOˆØ-<ˆÔ˜kÑ*Ý% oÐ7OÐQUÑVÔVÐð EJÄLÔDZÐd˜K¨¬Ô)?Ð@Ð@Ð`dÐØ)Ð+;Ð<Ðð	$Ý&Ô0°Ô1BÑCÔCÐÐøÝ�:Ð&ð 	$ð 	$ð 	$Ø#ÐÐÐð	$øøøàÐ# |Ð7GÔ7RÐ'RÐ'RØ0?Ð Ñ-ÝÔ)ØˆDÔØ”Ô$ðð à%ðð ñ
ô 
ˆðC	PØ×+Ò+Ð,AÀ5ÑIÔIÐIÐIÐIÐIÐIÐIÐIð "×2Ò2°5Ñ9Ô9Ð9Ð9Ð9Ð9Ð9Ð9ˆHð ð
 à#×*Ò*Ñ,Ô,ð
 ð   4Ô#9Ð9Ð9å—’ØPØ”IØñô ð ð
  �Øð rÝ—’ÐOÐQUÔQZÐ\aÔ\hÔ\pÑqÔqÐqØñ hmà(,×(:Ò(:¸8Ñ(DÔ(DÑ%�˜Xð (,×':Ò':¸8Ñ'DÔ'DÑ$�˜à+×3Ò3Ð4HÈ"ÑMÔM×SÒSÑUÔU�Ý!œv o°r¸<ÑHÔH×NÒNÑPÔP�Øð GÝ—K’KÐ ^Ð`dÔ`iÕknÐouÑkvÔkvÕx{ð  }Eñ  yFô  yFñ  Gô  Gð  Gð -1×,DÒ,DÀ\Ñ,RÔ,RÑ)�˜\Øð vÝ—K’KÐ WÐY]ÔYbÕdgÐhsÑdtÔdtÑuÔuÐuð !�	Ø×2Ò2°5´<Ô3GÑHÔHð WØ!Ô.µ+Ô2CÒCÐCØ(ð Dà +ð DðWØ^Ð^Ð^Ð^Ð^Ð^Ð^Ð^Ø1Ð1Ñ3Ô3ð 	BØ0Ð0Ð0Ð0Ý*,¬&°À"ÀlÑ*SÔ*SÐTYÐUYÐTYÔ*Z×*`Ò*`Ñ*bÔ*b˜KØ#.ð VÝ&0Ð1TÑ&UÔ&UÐ UÝ3:Ô3DØ 3¸+ð4ñ 4ô 4ð .ð .ð .ð .ð .ð .˜Nð (-§{¢{°>Ñ'BÔ'B˜HØ(0¯ª°[Ñ(AÔ(A˜IøøÝ$ð Wð Wð WÝŸšÐ'AÀ4Ä9ÈgÑVÔVÐVÐVÐVÐVÐVÐVøøøøðWøøøð ð !¥ i¡¤×!7Ò!7Ñ!9Ô!9ð !ð
!Ø"ŸmšmØ$)¤LÔ$8Ø'0Ø%5ð ,ñ ô ð ð ð ð ð ð ð ð!ÝœI iÑ0Ô0Ð0Ð0øÝ&ð !ð !ð !Ø ˜Dð!øøøøð!ÝœI iÑ0Ô0Ð0Ð0øÝ&ð !ð !ð !Ø ˜Dð!øøøøøøð  ð -Ý—K’KÐ HÈ$Ì)ÕUXÐYeÑUfÔUfÐhmÔhtÔh|Ñ}Ô}Ð}Ø#'×#8Ò#8Ø %¤Ô 4Ø ,Ø!&Ô!1Ø!1ð	 $9ñ $ô $ð ð ð ð ð ð �Fð %Ð$ VÑ,Ô,Ð,ð #×3Ò3Ñ5Ô5�ð ð mÝ—K’KÐ SÐUYÔU^Õ`cÐdjÑ`kÔ`kÑlÔlÐlØ+1ð hñ hÑ'�I˜xØ" Q’�Ý%œm¨KÑ8Ô8Ð8Ð8Ð8Ð8Ð8Ð8Ð8ðhÝŸšØ=Ø œIÝ,¨YÑ7Ô7Ø-5Ð=˜H S b SœM˜M¸2ñ	ô ð ð  ×1Ò1°)Ñ<Ô<ð Ø/3×/BÒ/BØ(-¬Ô(<Ø.7Ø4<Ð(F¨¨À$Ø)9ð	 0Cñ 0ô 0ð *ð *ð *ð *ð *ð *˜J˜Jð 04¯ªØ(-¬Ô(<Ø*3Ø4<Ð(F¨¨À$Ø)9ð	 0?ñ 0ô 0ð *ð *ð *ð *ð *ð *˜Jð  *Ô1ð gÝ"ŸLšLÐ)HÈ$Ì)ÐU_ÔUeÑfÔfÐfùøÝ$ð hð hð hÝŸšÐ%CÀTÄYÐPWÐbf˜ÑgÔgÐgÐgÐgÐgÐgÑgøøøøðhøøøð HÐGÐG�ØOÐOÐO�ØHÐHÐH�à,7ð !]ñ !]Ñ(�J Ø" Q’�Ý%œm¨KÑ8Ô8Ð8Ð8Ð8Ð8Ð8Ð8Ð8ð]Ý" :Ñ.Ô.Ô5×;Ò;Ñ=Ô=˜Ø +Ð-Ð-Ø15·²Ø(-¬Ô(<Ø+5Ø)9ð 2Añ 2ô 2ð ,ð ,ð ,ð ,ð ,ð ,˜L˜Lð
 ! KÐ/Ð/Ø15·²Ø(-¬Ô(<Ø+5Ø)9ð 2Añ 2ô 2ð ,ð ,ð ,ð ,ð ,ð ,˜L˜Lð
 ! KÐ/Ð/Ø15×1EÒ1EØ(-¬Ô(<Ø+5Ø)9ð 2Fñ 2ô 2ð ,ð ,ð ,ð ,ð ,ð ,˜L˜Lð 26×1CÒ1CØ(-¬Ô(<Ø*4Ø)9ð 2Dñ 2ô 2ð ,ð ,ð ,ð ,ð ,ð ,˜Lð  ,Ô3ð uÝ"ŸNšNÐ+OÐQUÔQZÐ\_ÐamÔasÑtÔtÐtùøÝ$ð ]ð ]ð ]ÝŸšÐ'EÀtÄyÐR[Ñ\Ô\Ð\Ð\Ð\Ð\Ð\Ñ\øøøøð]øøøð "-ð mñ m�IØ" Q’�Ý%œm¨KÑ8Ô8Ð8Ð8Ð8Ð8Ð8Ð8Ð8ðmÝ" 9™oœoÔ4×:Ò:Ñ<Ô<˜Ø +Ð-Ð-Ø"&×"6Ò"6Ø(-¬Ô(<Ø+4Ø)9ð #7ñ #ô #ð ð ð ð ð ð ð ð ð
 ! KÐ/Ð/Ø"&§/¢/Ø(-¬Ô(<Ø+4Ø)9ð #2ñ #ô #ð ð ð ð ð ð ð ð ð #'×"4Ò"4Ø(-¬Ô(<Ø*3Ø)9ð #5ñ #ô #ð ð ð ð ð ð ð øøõ
 %ð mð mð mÝŸšÐ%KÈTÌYÐXaÐckÑlÔlÐlÐlÐlÐlÐlÐløøøøðmøøøð 3EÐ\Ð.Ð.ÍdÐS[ÉnÌnÐJ\ˆMØ×+Ò+Ø(ØØ-:ÐYÕ!Ô)Ð)Õ@QÔ@Yñô ð ð ð ð ð ð ð ð ˜dÔ4Ð4Ñ4Ø $Ô 6× :Ò :¸;Ñ GÔ G�Ý—’ÐLÈdÌiÑXÔXÐXð Ô/×3Ò3°KÑ@Ô@�ØÐ&Ø—M’M‘O”O�OØ×"Ò"Ñ$Ô$Ð$ðØ%Ð%Ð%Ð%Ð%Ð%Ð%Ð%Ð%øÝÔ-ð ð ð Ø�Dðøøøð ×6Ò6°}ÀkÑRÔRÐRÐRÐRÐRÐRÐRÐRØð> $7Ð Ý�tÐ9Ñ:Ô:ð `Ø×:Ò:ØØ3ð ;ñ ô ��õ
 # 4Ð)CÀRÑHÔH×LÒLÈ[ÐZ^Ñ_Ô_�Ý˜Ñ!Ô!ð ðØ�H‘J”J�J�JøÝ ð ð ð Ø�Dðøøøð ×ÒÑ Ô Ð ðØ!Ð!Ð!Ð!Ð!Ð!Ð!Ð!Ð!øÝÔ)ð ð ð Ø�ðøøøðÝ˜4 Ñ/Ô/ð AØ×*Ò*¨5¬<Ô+?Ñ@Ô@Ð@Ð@Ð@Ð@Ð@Ð@Ð@øøÝð ð ð Ø�ðøøøð  Ô1×5Ò5°kÀ4ÑHÔHˆLØÐ'Ý—’Ø^Ø”Iñô ð ð Ô/×3Ò3°KÑ@Ô@�ØÐ&Ø—M’M‘O”O�OÝ$Ô0Ø×4Ò4°\À;ÑOÔOñô �
ð
 4>�Ô# KÑ0ðØÔ*×.Ò.¨zÑ:Ô:Ð:Ø×0Ò0°Ô1GÔ1OÑPÔPÐPÐPÐPøÝ ð ð ð à�D�Dðøøøõ  'Ô3Ñ5Ô5�ØÐ+°Ô0C×0GÒ0GÈÑ0TÔ0TÐXdÐ0dÐ0dØÔ+¨KÐ8Ø×+Ò+¨K¸Ð+ÑOÔOÐOÐOÐOñk 5øõ0 Ô%ð 	ð 	ð 	Ý"Ô/Ñ1Ô1ˆLÝ'Ô1ˆGØÐ# |¸4Ô;YÐ'YÐ'YÝ+Ô3�Ø×+Ò+Ð,DÀeÈWÑUÔUÐUÐUÐUÐUÐUÐUÐUØÝð 	ð 	ð 	Ø×+Ò+Ð,DÀeÕM^ÔMfÑgÔgÐgÐgÐgÐgÐgÐgÐgÝ�LŠLÐ:¸D¼IÀqÐSWˆLÑXÔXÐXðÝ! !™WœWÔ-�
Ý/2°1©v¬vÐQ�s 1™vœv d s dœ|˜|Ð;Q�ØLQÌLÔLbÐ#l K°´Ô1GÐ#HÐ#HÐhlÐ Ø—i’iØ!œLÔ0ðL¸*ð Lð LØ'ðLð Lð Lð .ð  ñ ô ð ð ð ð ð ð ð ð øõ ð ð ð Ø�ðøøøøøøøøøøøøð#	øøøð, $7Ð Ý�tÐ9Ñ:Ô:ð `Ø×:Ò:ØØ3ð ;ñ ô ��õ
 # 4Ð)CÀRÑHÔH×LÒLÈ[ÐZ^Ñ_Ô_�Ý˜Ñ!Ô!ð ðØ�H‘J”J�J�JøÝ ð ð ð Ø�Dðøøøð ×ÒÑ Ô Ð ðØ!Ð!Ð!Ð!Ð!Ð!Ð!Ð!Ð!øÝÔ)ð ð ð Ø�ðøøøðÝ˜4 Ñ/Ô/ð AØ×*Ò*¨5¬<Ô+?Ñ@Ô@Ð@Ð@Ð@Ð@Ð@Ð@Ð@øøÝð ð ð Ø�ðøøøð  Ô1×5Ò5°kÀ4ÑHÔHˆLØÐ'Ý—’Ø^Ø”Iñô ð ð Ô/×3Ò3°KÑ@Ô@�ØÐ&Ø—M’M‘O”O�OÝ$Ô0Ø×4Ò4°\À;ÑOÔOñô �
ð
 4>�Ô# KÑ0ðØÔ*×.Ò.¨zÑ:Ô:Ð:Ø×0Ò0°Ô1GÔ1OÑPÔPÐPÐPÐPøÝ ð ð ð à�D�Dðøøøõ  'Ô3Ñ5Ô5�ØÐ+°Ô0C×0GÒ0GÈÑ0TÔ0TÐXdÐ0dÐ0dØÔ+¨KÐ8Ø×+Ò+¨K¸Ð+ÑOÔOÐOÐOÐOøðA $7Ð Ý�tÐ9Ñ:Ô:ð `Ø×:Ò:ØØ3ð ;ñ ô ��õ
 # 4Ð)CÀRÑHÔH×LÒLÈ[ÐZ^Ñ_Ô_�Ý˜Ñ!Ô!ð ðØ�H‘J”J�J�JøÝ ð ð ð Ø�Dðøøøð ×ÒÑ Ô Ð ðØ!Ð!Ð!Ð!Ð!Ð!Ð!Ð!Ð!øÝÔ)ð ð ð Ø�ðøøøðÝ˜4 Ñ/Ô/ð AØ×*Ò*¨5¬<Ô+?Ñ@Ô@Ð@Ð@Ð@Ð@Ð@Ð@Ð@øøÝð ð ð Ø�ðøøøð  Ô1×5Ò5°kÀ4ÑHÔHˆLØÐ'Ý—’Ø^Ø”Iñô ð ð Ô/×3Ò3°KÑ@Ô@�ØÐ&Ø—M’M‘O”O�OÝ$Ô0Ø×4Ò4°\À;ÑOÔOñô �
ð
 4>�Ô# KÑ0ðØÔ*×.Ò.¨zÑ:Ô:Ð:Ø×0Ò0°Ô1GÔ1OÑPÔPÐPÐPøÝ ð ð ð à�Dðøøøõ  'Ô3Ñ5Ô5�ØÐ+°Ô0C×0GÒ0GÈÑ0TÔ0TÐXdÐ0dÐ0dØÔ+¨KÐ8Ø×+Ò+¨K¸Ð+ÑOÔOÐOÐOøøøsá  Á8B ÂB(Â'B(Ã&G)o4 ËBM. Í-o4 Í.
N#Í8!NÎo4 ÎN#Î#&o4 Ï
(P Ï3P Ðo4 Ð
PÐo4 ÐPÐo4 ÐP?ÐP/Ð.P?Ð/
P<Ð9P?Ð;P<Ð<P?Ð?C#o4 Ô#CW=×;o4 ×=
X5Ø#X0Ø*o4 Ø0X5Ø57o4 Ù-D]2Ý0o4 Ý2
^(Ý<!^#Þo4 Þ#^(Þ((o4 ßB(a:á9o4 á:
b0â"b+â&o4 â+b0â0Co4 æf æo4 æf æo4 æf æ o4 è
h* è*
h7è6h7éi éi*é)i*é.5j$ ê$
j1ê0j1í9n î
nînï2~ ï4A,uñ A
uò+Bt3ô2uô3
u ô=uô?u õ uõ~ õuõ~ ö/
v: ö:
w÷w÷w( ÷(w:÷9w:÷>5x4 ø4
yù yû$9| ü
|-ü,|-þAAF2ÿ"
-ÿ,AF2ÿ-
:ÿ7AF2ÿ9:ÿ:AF2Á@A@Á@AF2Á@A@-Á@*AF2Á@,A@-Á@-AF2Á@15AA'ÁA&AF2ÁA'
AA4ÁA1AF2ÁA3AA4ÁA4B"AF2ÁD9AEÁEAF2ÁE
AEÁEAF2ÁEAEÁEAAF2c              ƒ   óê  K  — d}t          |¦  «        D ]a}d„ | j        D ¦   «         }|s nK|D ]0}| j                             |¦  «         |                     ¦   «          Œ1t          j        |ddiŽƒ d{V —† Œb| j                             ¦   «          | j                             ¦   «          | j                             ¦   «          | j	                             ¦   «          | j
                             ¦   «          dS )zÜCancel any in-flight background message-processing tasks.

        Used during gateway shutdown/replacement so active sessions from the old
        process do not keep running after adapters are being torn down.
        é   c                 ó:   — g | ]}|                      ¦   «         °|‘ŒS rß   )rË  )rq   rÌ  s     r   rm  z?BasePlatformAdapter.cancel_background_tasks.<locals>.<listcomp>í	  s%   € ÐPÐPÐP˜dÀDÇIÂIÁKÄKÐP�TÐPÐPÐPr   Úreturn_exceptionsTN)r  rÃ  rÅ  rt  rÞ  r  Úgatherr  rÁ  rÀ  r¿  )r…  ÚMAX_DRAIN_ROUNDSrO   ÚtasksrÌ  s        r   Úcancel_background_tasksz+BasePlatformAdapter.cancel_background_tasksÜ	  s  è è € ð ÐÝÐ'Ñ(Ô(ð 	Að 	AˆAØPÐP dÔ&<ÐPÑPÔPˆEØð Ø�Øð ð �ØÔ.×2Ò2°4Ñ8Ô8Ð8Ø—’‘”��Ý”. %Ð@¸4Ð@Ð@Ð@Ð@Ð@Ð@Ð@Ð@Ð@Ð@ð 	Ô×$Ò$Ñ&Ô&Ð&ØÔ&×,Ò,Ñ.Ô.Ð.ØÔ×!Ò!Ñ#Ô#Ð#ØÔ×$Ò$Ñ&Ô&Ð&ØÔ×#Ò#Ñ%Ô%Ð%Ð%Ð%r   c                 óR   — || j         v o| j         |                              ¦   «         S )z3Check if there's a pending interrupt for a session.)r¿  rƒ  rÏ  s     r   Úhas_pending_interruptz)BasePlatformAdapter.has_pending_interruptü	  s)   € à˜dÔ3Ð3Ðc¸Ô8MÈkÔ8Z×8aÒ8aÑ8cÔ8cÐcr   c                 ó8   — | j                              |d¦  «        S )z0Get and clear any pending message for a session.N)rÀ  r™  rÏ  s     r   Úget_pending_messagez'BasePlatformAdapter.get_pending_message 
  s   € àÔ%×)Ò)¨+°tÑ<Ô<Ð<r   ÚdmÚ	chat_nameÚ	chat_typeÚuser_idÚ	user_namerç  Ú
chat_topicÚuser_id_altÚchat_id_altÚis_botÚguild_idÚparent_chat_idc                 óf  — |�|                      ¦   «         sd}t          | j        t          |¦  «        |||rt          |¦  «        nd||rt          |¦  «        nd|r|                      ¦   «         nd||	|
|rt          |¦  «        nd|rt          |¦  «        nd|rt          |¦  «        nd¬¦  «        S )z2Helper to build a SessionSource for this platform.N)rB   rÕ  rJ  rK  rL  rM  rç  rN  rO  rP  rQ  rR  rS  rx  )rH   r½   rB   r]   )r…  rÕ  rJ  rK  rL  rM  rç  rN  rO  rP  rQ  rR  rS  rx  s                 r   Úbuild_sourcez BasePlatformAdapter.build_source
  sÍ   € ð$ Ð!¨*×*:Ò*:Ñ*<Ô*<Ð!ØˆJÝØ”]Ý˜‘L”LØØØ$+Ð5•C˜‘L”L�L°ØØ(1Ð;•c˜)‘n”n�n°tØ-7ÐA�z×'Ò'Ñ)Ô)Ð)¸TØ#Ø#ØØ&.Ð8•S˜‘]”]�]°DØ2@ÐJ�3˜~Ñ.Ô.Ð.ÀdØ*4Ð>•s˜:‘”�¸$ð
ñ 
ô 
ð 	
r   c              ƒ   ó
   K  — dS )z§
        Get information about a chat/channel.
        
        Returns dict with at least:
        - name: Chat name
        - type: "dm", "group", "channel"
        Nrß   r×  s     r   Úget_chat_infoz!BasePlatformAdapter.get_chat_info)
  s   è è € ð 	ˆr   c                 ó   — |S )zú
        Format a message for this platform.
        
        Override in subclasses to handle platform-specific formatting
        (e.g., Telegram MarkdownV2, Discord markdown).
        
        Default implementation returns content as-is.
        rß   )r…  r  s     r   Úformat_messagez"BasePlatformAdapter.format_message4
  s	   € ð ˆr   é   Ú
max_lengthr   zCallable[[str], int]c                 óæ  ‡— |pt           } || ¦  «        |k    r| gS d}d}g }| }d}|�rŠ|�d|› d�nd}	||z
   ||	¦  «        z
   ||¦  «        z
  }
|
dk     r|dz  }
 ||	¦  «         ||¦  «        z   ||z
  k    r|                     |	|z   ¦  «         �n |t           urt          ||
|¦  «        }n|
}|d|…         }|                     d¦  «        }||dz  k     r|                     d	¦  «        }|dk     r|}|d|…         }|                     d
¦  «        |                     d¦  «        z
  }|dz  dk    r¥|                     d
¦  «        }|dk    r;||dz
           dk    r,|                     d
d|¦  «        }|dk    r||dz
           dk    °,|dk    rI|                     d	d|¦  «        }|                     dd|¦  «        }t          ||¦  «        }||dz  k    r|}|d|…         }||d…                              ¦   «         }|	|z   }|du}|pd}|                     d¦  «        D ]n}|                     ¦   «         }| 	                    d¦  «        rC|rd}d}Œ2d}|dd…                              ¦   «         }|r|                     ¦   «         d         nd}Œo|r||z  }|}nd}|                     |¦  «         |�°Št          |¦  «        dk    r*t          |¦  «        Šˆfd„t          |¦  «        D ¦   «         }|S )a9  
        Split a long message into chunks, preserving code block boundaries.

        When a split falls inside a triple-backtick code block, the fence is
        closed at the end of the current chunk and reopened (with the original
        language tag) at the start of the next chunk.  Multi-chunk responses
        receive indicators like ``(1/3)``.

        Args:
            content: The full message content
            max_length: Maximum length per chunk (platform-specific)
            len_fn: Optional length function for measuring string length.
                     Defaults to ``len`` (Unicode code-points).  Pass
                     ``utf16_len`` for platforms that measure message
                     length in UTF-16 code units (e.g. Telegram).

        Returns:
            List of message chunks
        é
   z
```Nz```r�  rX   r   r
   Ú ú`z\`r   ú\rä   FTr5   c                 ó2   •— g | ]\  }}|› d |dz   › d‰› d�‘ŒS )z (r   rÆ   rî   rß   )rq   ÚiÚchunkÚtotals      €r   rm  z8BasePlatformAdapter.truncate_message.<locals>.<listcomp>½
  sG   ø€ ð ð ð Ù19°°E�5Ð,Ð,˜A ™EÐ,Ð, EÐ,Ð,Ð,ðð ð r   )r   rA  r   Úrfindrd   Úmaxr\  rx   rH   ra   Ú	enumerate)r  r[  r   Ú_lenÚINDICATOR_RESERVEÚFENCE_CLOSEÚchunksÚ	remainingÚ
carry_langÚprefixÚheadroomÚ	_cp_limitÚregionÚsplit_atr�   Úbacktick_countÚlast_btÚ
safe_splitÚnl_splitÚ
chunk_bodyÚ
full_chunkÚin_codeÚlangrM   ÚstrippedÚtagrd  s                             @r   Útruncate_messagez$BasePlatformAdapter.truncate_message?
  sµ  ø€ ð2 ˆ}�ˆØˆ4�‰=Œ=˜JÒ&Ð&Ø�9ÐàÐØˆàˆØˆ	ð %)ˆ
àñ S	&ð .8Ð-CÐ)˜:Ð)Ð)Ð)Ð)ÈˆFð "Ð$5Ñ5¸¸¸V¹¼ÑDÀtÀtÈKÑGXÔGXÑXˆHØ˜!Š|ˆ|Ø%¨™?�ð ˆt�F‰|Œ|˜d˜d 9™oœoÑ-°Ð>OÑ1OÒOÐOØ—’˜f yÑ0Ñ1Ô1Ð1Ùð �3ˆˆå.¨y¸(ÀDÑIÔI�	�	à$�	Ø˜z 	˜zÔ*ˆFØ—|’| DÑ)Ô)ˆHØ˜) q™.Ò(Ð(Ø!Ÿ<š<¨Ñ,Ô,�Ø˜!Š|ˆ|Ø$�ð " ) 8 )Ô,ˆIØ&Ÿ_š_¨SÑ1Ô1°I·O²OÀEÑ4JÔ4JÑJˆNØ Ñ! QÒ&Ð&à#Ÿ/š/¨#Ñ.Ô.�Ø ’k�k i°¸!±Ô&<ÀÒ&DÐ&DØ'Ÿošo¨c°1°gÑ>Ô>�Gð  ’k�k i°¸!±Ô&<ÀÒ&DÐ&Dà˜Q’;�;à!*§¢°°a¸Ñ!AÔ!A�JØ(Ÿš¨t°Q¸Ñ@Ô@�HÝ!$ Z°Ñ!:Ô!:�JØ! I°¡NÒ2Ð2Ø#-˜à" 9 H 9Ô-ˆJØ! ( ) )Ô,×3Ò3Ñ5Ô5ˆIà *Ñ,ˆJð !¨Ð,ˆGØÐ# ˆDØ"×(Ò(¨Ñ.Ô.ð 	=ð 	=�ØŸ:š:™<œ<�Ø×&Ò& uÑ-Ô-ð =Øð =Ø"'˜Ø!˜˜à"&˜Ø& q r rœl×0Ò0Ñ2Ô2˜Ø14Ð<˜sŸyšy™{œ{¨1œ~˜~¸"˜øàð "à˜kÑ)�
Ø!�
�
à!�
à�MŠM˜*Ñ%Ô%Ð%ðg ñ S	&õl ˆv‰;Œ;˜Š?ˆ?Ý˜‘K”KˆEðð ð ð Ý=FÀvÑ=NÔ=Nðñ ô ˆFð ˆr   )r   N)NNro   )NNN)r  NN)NNr
   r  )NrI  NNNNNNFNNN)rZ  N)^r  r_  r`  ra  r¼   r»   rË  Úpropertyr‘  rÎ  rµ   r]   rÐ  rÒ  rÔ  rØ  r·   r¸   rÜ  rè  rì  rñ  rõ  r  r  rN  r
  ÚMessageHandlerr  rt  r  r¶   r  r   r  r  r³   r”  r  r  r�  r  r   r"  r(  r+  Ústaticmethodr/  r¹   r´   rH  rL  rN  rQ  rU  rX  ra  r~  Úfloatr  rÓ  r‰  r‹  r�  r�  rc   r–  r›  r�  rl  r¡  r¥  r¬  r°  r¾  rŸ  rÉ  rÍ  rÐ  r×  rß  rã  ré  rô  r  rÕ  rD  rF  rH  r½   rU  rW  rY  r}  rß   r   r   rž  rž  Ù  s]  € € € € € € ðð ð1)˜~ð 1)¸ð 1)ð 1)ð 1)ð 1)ðf ð5 ð 5ð 5ð 5ñ „Xð5ð ð) X¨c¤]ð )ð )ð )ñ „Xð)ð ð& (¨3¤-ð &ð &ð &ñ „Xð&ð ð+ tð +ð +ð +ñ „Xð+ð,°ð ,¸ð ,ð ,ð ,ð ,ð,¨xÐ9NÐ8OÐQZÐ[_ÔQ`ÐcgÑQgÐ8gÔ/hð ,Ðmqð ,ð ,ð ,ð ,ð	ð 	ð 	ð 	ðð ð ð ð Sð °3ð Àdð Ètð ð ð ð ð ð ð ð ð¨Cð ¸3ð Èsð ÐW[ð ð ð ð ð(,ð ,ð ,ð ,ð ð+�cð +ð +ð +ñ „Xð+ð ð˜dð ð ð ñ „Xðð(¨>ð (¸dð (ð (ð (ð (ð-°¸À<ÐQTÐBUÐW`ÐaeÔWfÐBfÔ9gÔ0hð -Ðmqð -ð -ð -ð -ð,¨sð ,°tð ,ð ,ð ,ð ,ð ð˜tð ð ð ñ „^ðð ðð ð ñ „^ðð ð
 #'Ø-1ðð àðð ðð ˜3”-ð	ð
 ˜4  S œ>Ô*ðð 
ðð ð ñ „^ðð6 $)Ð˜DÐ(Ð(Ñ(ð ð@ð @ð @àð@ð ð@ð ð	@ð ð@ð 
ð@ð @ð @ð @ð:ð ¨ð Àð ð ð ð ð¨ð °ð ð ð ð ð "&Ø"&Ø-1ðQð QàðQð ðQð ˜#”ð	Qð
 ˜3”-ðQð ˜4  S œ>Ô*ðQð 
ðQð Qð Qð Qð. "&Ø"&Ø-1ðFð FàðFð ðFð ˜#”ð	Fð
 ˜3”-ðFð ˜4  S œ>Ô*ðFð 
ðFð Fð Fð Fð" ð&˜sð & tð &ð &ð &ñ „\ð&ð
 ð- ð -¨¨d°5¸¸c¸´?Ô.CÀSÐ.HÔ(Ið -ð -ð -ñ „\ð-ðf "&Ø"&ðQð QàðQð ðQð ˜#”ð	Qð
 ˜3”-ðQð 
ðQð Qð Qð Qð(WàðWð ðWð
 
ðWð Wð Wð Wð$ "&Ø"&ðQð QàðQð ðQð ˜#”ð	Qð
 ˜3”-ðQð 
ðQð Qð Qð Qð. "&Ø#'Ø"&ðQð QàðQð ðQð ˜#”ð	Qð
 ˜C”=ðQð ˜3”-ðQð 
ðQð Qð Qð Qð0 "&Ø"&ðQð QàðQð ðQð ˜#”ð	Qð
 ˜3”-ðQð 
ðQð Qð Qð Qð( ð'˜sð ' u¨T°%¸¸T¸	Ô2BÔ-CÀSÐ-HÔ'Ið 'ð 'ð 'ñ „\ð'ðR ðA Sð A¨U°4¸´9¸c°>Ô-Bð Að Að Añ „\ðAðL ØØ+/ð,1ð ,1àð,1ð ð,1ð
 ”M DÑ(ð,1ð 
ð,1ð ,1ð ,1ð ,1ð\)¨Sð )°Tð )ð )ð )ð )ð-¨cð -°dð -ð -ð -ð -ð	¸Cð 	È#ð 	ÐRVð 	ð 	ð 	ð 	ð  "&ðUð Uð UàðUð ðUð
 ˜$‘JðUð 
ðUð Uð Uð Uð. "&ð	2ð 2ð 2àð2ð ˜$‘Jð	2ð
 
�D‰ð2ð 2ð 2ð 2ð6=¨|ð =Àð =ð =ð =ð =ð@°,ð @ÐIZð @Ð_cð @ð @ð @ð @ðO°Cð OÀð OÈsð OÐW[ð Oð Oð Oð Oð ðH 8¨C¤=ð H°Tð Hð Hð Hñ „\ðHð ð	_ ¨#¤ð 	_°4ð 	_ð 	_ð 	_ñ „\ð	_ð #'ØØØðPð PàðPð ðPð ˜3”-ð	Pð
 ðPð ðPð ðPð 
ðPð Pð Pð Pðd ð h¨s¤mð ¸sð Àsð ð ð ñ „\ðð6 *.ð	/ð /ð /àð/ð ˜œÔ&ð	/ð
 
ð/ð /ð /ð /ð(%°#ð %¸$ð %ð %ð %ð %ð"°Cð ¸Dð ð ð ð ð> 48ðð ð àðð ðð
 " '¤-Ô0ðð 
ðð ð ð ðH #Ø $ð$5ð $5ð $5àð$5ð ð	$5ð
 ð$5ð 
ð$5ð $5ð $5ð $5ðLCàðCð ”}ðCð 
ð	Cð Cð Cð Cð"8Tàð8Tð ð8Tð ð	8Tð
 
ð8Tð 8Tð 8Tð 8Tðti;¨,ð i;¸4ð i;ð i;ð i;ð i;ðV ð@˜eð @ð @ð @ñ „\ð@ð$hP°|ð hPÐRUð hPÐZ^ð hPð hPð hPð hPðT&ð &ð &ð &ð@d°ð d¸ð dð dð dð dð=¨sð =°xÀÔ7Mð =ð =ð =ð =ð $(ØØ!%Ø#'Ø#'Ø$(Ø%)Ø%)ØØ"&Ø(,Ø$(ð#
ð #
àð#
ð ˜C”=ð#
ð ð	#
ð
 ˜#”ð#
ð ˜C”=ð#
ð ˜C”=ð#
ð ˜S”Mð#
ð ˜c”]ð#
ð ˜c”]ð#
ð ð#
ð ˜3”-ð#
ð ! œð#
ð ˜S”Mð#
ð 
ð#
ð #
ð #
ð #
ðJ ð¨3ð °4¸¸S¸´>ð ð ð ñ „^ðð	 cð 	¨cð 	ð 	ð 	ð 	ð ð Ø37ðAð AØðAàðAð Ð/Ô0ðAð 
ˆcŒð	Að Að Añ „\ðAð Að Ar   rž  ro   )rÀ   )ré   )ré   r
   )r  )r3  )r3  r
   )r;  )hra  r  r  r   r	  ru   rµ  r?  Úsocketr$   rC   rA   rñ   Úabcr   r   Úurllib.parser   Úutilsr   r
  r  r©   r]   rc   r   r   r   r‘  r1   rU   r˜  rj   rŒ   rz   r‡   rÂ  r�   rš   r²  r¬   r®   Údataclassesr¯   r°   r±   Úpathlibr²   Útypingr³   r´   rµ   r¶   r·   r¸   r¹   Úenumrº   Ú_PathrÊ   ÚinsertÚ__file__rO  rÛ   Úgateway.configr»   r¼   Úgateway.sessionr½   r¾   Úhermes_constantsr¿   Ú*GATEWAY_SECRET_CAPTURE_UNSUPPORTED_MESSAGErÎ   r×   rÝ   rà   Úbytesrè   rù   r  r-  r1  r2  r7  r9  rA  ÚSUPPORTED_VIDEO_TYPESrB  rE  rH  ÚSUPPORTED_DOCUMENT_TYPESrI  rS  rU  rW  rl  rt  r”  r¥  rª  r  rµ  rž  rß   r   r   ú<module>r”     så  ððð ð €€€Ø €€€Ø Ð Ð Ð Ø €€€Ø 	€	€	€	Ø €€€Ø 	€	€	€	Ø Ð Ð Ð Ø Ð Ð Ð Ø 
€
€
€
Ø €€€Ø #Ð #Ð #Ð #Ð #Ð #Ð #Ð #Ø !Ð !Ð !Ð !Ð !Ð !à %Ð %Ð %Ð %Ð %Ð %à	ˆÔ	˜8Ñ	$Ô	$€ð+�ð +˜ð +ð +ð +ð +ð #ð ¨cð °cð ð ð ð ð&˜#ð  sð °sð ð ð ð ð&  ð  ¨ð  ð  ð  ð  ðF! C¨$¡Jð !ð !ð !ð !ðH5˜Cð 5 E¨#¨s°T©z¨/Ô$:ð 5ð 5ð 5ð 5ð(˜4 œ9ð ð ð ð ð&Að &A 3ð &A¨cð &A¸¸t¹ð &AÈtð &Að &Að &Að &AðR c¨D°¬I¡o¸¸cÀ3¸h¼Ñ&GÈ#ÈcÌ(Ñ&RÐUYÑ&Yð Ð^bð ð ð ð ð. $(ðð IMðð ð Ø˜D‘jðð ˜˜Sœ	‘/ E¨#¨s¨(¤OÑ3°c¸#´hÑ>ÀÑEðð 	ˆ4�Zð	ð ð ð ðD  C¨$¡Jð  °4ð  ð  ð  ð  ð<$¨¨d©
ð $°u¸TÀ4¸ZÔ7Hð $ð $ð $ð $ðB )Ð (Ð (Ð (Ð (Ð (Ð (Ð (Ø Ð Ð Ð Ð Ð Ø Ð Ð Ð Ð Ð Ø HÐ HÐ HÐ HÐ HÐ HÐ HÐ HÐ HÐ HÐ HÐ HÐ HÐ HÐ HÐ HÐ HÐ HØ Ð Ð Ð Ð Ð à !Ð !Ð !Ð !Ð !Ð !Ø „‡‚��3�3�u�u˜X‘”×.Ò.Ñ0Ô0Ô8¸Ô;Ñ<Ô<Ñ =Ô =Ð =à 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ð 3Ø <Ð <Ð <Ð <Ð <Ð <Ð <Ð <Ø +Ð +Ð +Ð +Ð +Ð +ðbð +ð"&ð "&˜#ð "&¨ð "&°Sð "&ð "&ð "&ð "&ðJð ð ð6 !�. °Ñ?Ô?€ð˜Tð ð ð ð ð˜Eð  dð ð ð ð ð"ð  ð ¨Sð ¸cð ð ð ð ð88ð 8 Cð 8¨cð 8ÀSð 8ÐQTð 8ð 8ð 8ð 8ðvð  sð °Cð ð ð ð ð8 !�. °Ñ>Ô>€ð˜Tð ð ð ð ðð  ð ¨Sð ¸cð ð ð ð ð$8ð 8 Cð 8¨cð 8ÀSð 8ÐQTð 8ð 8ð 8ð 8ðD !�. °Ñ?Ô?€ð ØØØØðð Ð ð˜Tð ð ð ð ðð  ð ¨Sð ¸cð ð ð ð ð  $�^Ð$5Ð7GÑHÔHÐ ð ØØØØØVØPØXð	ð 	Ð ð ð ð ð ð ð Eð °Sð ¸Sð ð ð ð ð@ð ¨#ð °sð ð ð ð ð*
ð 
ð 
ð 
ð 
�$ñ 
ô 
ð 
ðð ð ð ð ˜ñ ô ð ð ðLð Lð Lð Lð Lñ Lô Lñ „ðLð^ ðð ð ð ð ñ ô ñ „ðð ð6*ð 6*ð 6*Ø˜3 Ð,Ô-ð6*àð6*ð ð6*ð
 ð6*ð 
ð6*ð 6*ð 6*ð 6*ð@
Ð ð ˜<˜.¨)°H¸S´MÔ*BÐBÔC€ð !ðð Øðàðð �T‰zðð 	ˆ4�Zð	ð ð ð ð<hð hð hð hð h˜#ñ hô hð hð hð hr   