Ë
    âß²j q  ã                   ó  — 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m	Z	 ddl
mZmZmZ ddlmZ ddlmZ  ej$                  e«      Z G d„ d«      Zej,                  Zd	eej0                     d
dfd„Zdeeef   dededed
df
d„Z ej:                  dej<                  «      df ej:                  dej<                  «      df ej:                  dej<                  «      df ej:                  dej<                  «      df ej:                  d«      df ej:                  d«      df ej:                  dej>                  «      df ej:                  d«      df ej:                  d«      dff	Z ded
efd„Z! G d„ d «      Z"d!ed
efd"„Z# ej:                  d#«      Z$d$ed
e%fd%„Z&d$ed
e'e   fd&„Z(d'e'e   d
efd(„Z)ded
efd)„Z*ddddd*œd+ed,edz  d-edz  d.d/d0d1d
d2fd3„Z+ded
e%fd4„Z,ded
e%fd5„Z-ded
e%fd6„Z.d$ed
e%fd7„Z/ded
e%fd8„Z0 ej:                  d9«      Z1d:„ Z2dUd;„Z3ded
d<fd=„Z4d>ed?ed
efd@„Z5dVdA„Z6dVdB„Z7dWdedDe%dEed
dFfdG„Z8dXdH„Z9dUdIdCdJœdK„Z:dUdL„Z;dM„ Z<dNZ=dOZ>d
e%fdP„Z?ddQl
m@Z@ ddRl
mAZA ddlZddlZ G dS„ dT«      ZBy)YuÆ   Shared helpers for gateway platform adapters: message dedup, markdown
stripping, thread participation tracking, GFM table â†’ bullets, mention-pattern
compilation, and fence-aware markdown chunking.é    N)ÚPath)ÚAnyÚMutableMappingÚOptional)ÚMessageEvent)Úatomic_json_writec                   óX   — e Zd ZdZddedefd„Zdedefd„Z	dedefd„Z
dedd	fd
„Zd„ Zy	)ÚMessageDeduplicatorzRTTL-based message deduplication cache (``if dedup.is_duplicate(msg_id): return``).Úmax_sizeÚttl_secondsc                 ó.   — i | _         || _        || _        y ©N)Ú_seenÚ	_max_sizeÚ_ttl)Úselfr   r   s      ú>/home/ubuntu/.hermes/hermes-agent/gateway/platforms/helpers.pyÚ__init__zMessageDeduplicator.__init__   s   € Ø')ˆŒ
Ø!ˆŒØˆ�	ó    Úmsg_idÚreturnc                 ón  — |syt        j                   «       }|| j                  v r-|| j                  |   z
  | j                  k  ry| j                  |= || j                  |<   t        | j                  «      | j                  kD  r¬|| j                  z
  }| j                  j                  «       D ��ci c]  \  }}||kD  sŒ||“Œ c}}| _        t        | j                  «      | j                  kD  rBt        t        | j                  j                  «       d„ ¬«      | j                   d «      | _        yc c}}w )z?Return True if *msg_id* was already seen within the TTL window.FTc                 ó   — | d   S )Né   © )Úitems    r   ú<lambda>z2MessageDeduplicator.is_duplicate.<locals>.<lambda>+   s   € ÈdÐSTÉg€ r   )ÚkeyN)Útimer   r   Úlenr   ÚitemsÚdictÚsorted)r   r   ÚnowÚcutoffÚkÚvs         r   Úis_duplicatez MessageDeduplicator.is_duplicate   sô   € áØÜ�i‰i‹kˆØ�T—Z‘ZÑØ�T—Z‘Z Ñ'Ñ'¨$¯)©)Ò3ØØ—
‘
˜6Ð"Ø ˆ�
‰
�6ÑÜˆt�z‰z‹?˜TŸ^™^Ò+Ø˜4Ÿ9™9‘_ˆFØ+/¯:©:×+;Ñ+;Ó+=×L¡4 1 aÀÀVÃ˜!˜Q™$ÓLˆDŒJÜ�4—:‘:‹ §¡Ò/ä!¤&¨¯©×)9Ñ)9Ó);ÑAUÔ"VÐX\×XfÑXfÐWfÐWgÐ"hÓi�”
Øùó	 Ms   Â0D1Â>D1c                 ó¬   — |r| j                   j                  |«      nd}|€yt        j                  «       |z
  | j                  k  ry| j                   |= y)zBReturn whether *msg_id* is live in the cache without inserting it.NFT)r   Úgetr   r   )r   r   Úseen_ats      r   ÚcontainszMessageDeduplicator.contains.   sI   € á,2�$—*‘*—.‘. Ô(¸ˆØˆ?ØÜ�9‰9‹;˜Ñ  4§9¡9Ò,ØØ�J‰J�vÐØr   Nc                 ó<   — | j                   j                  |d«       y)z<Release a claimed message ID after cancelled/failed handoff.N)r   Úpop)r   r   s     r   ÚdiscardzMessageDeduplicator.discard8   s   € à�
‰
�‰�v˜tÕ$r   c                 ó8   — | j                   j                  «        y r   )r   Úclear©r   s    r   r1   zMessageDeduplicator.clear<   s   € Ø�
‰
×ÑÕr   )iÐ  i,  )Ú__name__Ú
__module__Ú__qualname__Ú__doc__ÚintÚfloatr   ÚstrÚboolr(   r,   r/   r1   r   r   r   r
   r
      sV   „ Ù\ñ  ð  ¸%ó  ð
 3ð ¨4ó ð$˜sð  tó ð%˜cð % dó %ór   r
   Útaskr   c              ƒ   ó  K  — | �| j                  «       ry| j                  «        | t        j                  «       ur<t	        j
                  t        j                  t        «      5  | ƒ d{  –—†  ddd«       yy7 Œ# 1 sw Y   yxY w­w)a  Cancel *task* and wait for it to unwind. ``None``/finished tasks are no-ops; awaiting the
    current task would deadlock, so a self-cancel only requests cancellation. Exceptions the task
    dies with are swallowed: at teardown nobody is left to handle them.N)ÚdoneÚcancelÚasyncioÚcurrent_taskÚ
contextlibÚsuppressÚCancelledErrorÚ	Exception)r;   s    r   Úcancel_taskrE   F   sq   è ø€ ð €|�t—y‘y”{ØØ‡K�K„MØ”7×'Ñ'Ó)Ñ)Ü× Ñ ¤×!7Ñ!7¼ÓCñ 	Ø�JˆJ÷	ð 	ð *àø÷	ð 	üs0   ‚A"BÁ$A;Á*A9Á+A;Á/
BÁ9A;Á;BÂ BÚstorer   ÚvalueÚcapc                 ó˜   — | j                  |d«       || |<   t        | «      |kD  r%| t        t        | «      «      = t        | «      |kD  rŒ$yy)z°Insert into an insertion-ordered mapping with a hard size bound, evicting the oldest keys. A
    re-put moves the key to the newest position so live entries outlast stale ones.N)r.   r    ÚnextÚiter)rF   r   rG   rH   s       r   Úbounded_putrL   R   sF   € ð 
‡I�Iˆc�4ÔØ€Eˆ#�JÜ
ˆe‹*�sÒ
Ø”$”t˜E“{Ó#Ð$ô ˆe‹*�sÕ
r   z\*\*(.+?)\*\*z\1z	\*(.+?)\*z \b__(?![\s_])(.+?)(?<![\s_])__\bz\b_(?![\s_])(.+?)(?<![\s_])_\bz```[a-zA-Z0-9_+-]*\n?Ú z`(.+?)`z
^#{1,6}\s+z\[([^\]]+)\]\([^\)]+\)z\n{3,}ú

Útextc                 ób   — t         D ]  \  }}|j                  || «      } Œ | j                  «       S )zIStrip markdown formatting for plain-text platforms (SMS, iMessage, etc.).)Ú_STRIP_RULESÚsubÚstrip)rO   ÚpatternÚrepls      r   Ústrip_markdownrV   j   s1   € ä%ò '‰ˆ�Ø�{‰{˜4 Ó&‰ð'à�:‰:‹<Ðr   c                   ó”   — e Zd ZdZdZddedefd„Zdefd„Z	de
e   fd„Zdd
„Zdedefd„Zdedd	fd„Zdedd	fd„Zdedefd„Zdd„Zy	)ÚThreadParticipationTrackerzªPersistent set of threads the bot has participated in (``<platform>_threads.json``);
    ``thread_id in tracker`` checks membership, ``tracker.mark(thread_id)`` persists.éô  Úplatform_nameÚmax_trackedc                 óâ   — || _         || _        t        j                  «       | _        t        j                  «       | _        t        j                  d„ | j                  «       D «       «      | _	        y )Nc              3   ó2   K  — | ]  }t        |«      –— Œ y ­wr   )r9   )Ú.0Úts     r   ú	<genexpr>z6ThreadParticipationTracker.__init__.<locals>.<genexpr>‡   s   è ø€ Ò6TÀ!´s¸1·vÑ6Tùs   ‚)
Ú	_platformÚ_max_trackedÚ	threadingÚLockÚ_lockÚ_io_lockr"   ÚfromkeysÚ_loadÚ_threads)r   rZ   r[   s      r   r   z#ThreadParticipationTracker.__init__w   sK   € Ø&ˆŒØ'ˆÔô —^‘^Ó%ˆŒ
Ü!Ÿ™Ó(ˆŒÜ)-¯©Ñ6TÀtÇzÁzÃ|Ô6TÓ)Tˆ�r   r   c                 ó<   — ddl m}  |«       | j                  › d�z  S )Nr   )Úget_hermes_homez_threads.json)Úhermes_constantsrk   ra   )r   rk   s     r   Ú_state_pathz&ThreadParticipationTracker._state_path‰   s   € Ý4ÙÓ  d§n¡nÐ%5°]Ð#CÑCÐCr   c                 óì   — 	 t        j                  | j                  «       j                  d¬«      «      }t        |t        «      r|D �cg c]  }t        |«      ‘Œ c}S g S # t        $ r g cY S w xY wc c}w )Nzutf-8)Úencoding)ÚjsonÚloadsrm   Ú	read_textrD   Ú
isinstanceÚlistr9   )r   ÚdataÚ	thread_ids      r   rh   z ThreadParticipationTracker._load�   sk   € ð	Ü—:‘:˜d×.Ñ.Ó0×:Ñ:ÀGÐ:ÓLÓMˆDô :DÀDÌ$Ô9O°Ö5 9”�I•Ò5ÐWÐUWÐWøô ò 	ØŠIð	üâ5s   ‚3A  Á	A1Á A.Á-A.Nc                 ón  — | j                   5  | j                  5  t        | j                  «      }t	        |«      | j
                  kD  r*|| j
                   d  }t        j                  |«      | _        d d d «       t        | j                  «       d ¬«       d d d «       y # 1 sw Y   Œ.xY w# 1 sw Y   y xY w)N)Úindent)
rf   re   rt   ri   r    rb   r"   rg   r   rm   )r   Úthread_lists     r   Ú_savez ThreadParticipationTracker._save”   s¢   € Ø�]‰]ñ 	LØ—‘ñ ?Ü" 4§=¡=Ó1�Ü�{Ó# d×&7Ñ&7Ò7Ø"-¨t×/@Ñ/@Ð.@Ð.AÐ"B�KÜ$(§M¡M°+Ó$>�D”M÷	?ô
 ˜d×.Ñ.Ó0°+ÀdÕK÷	Lð 	L÷?ð ?ú÷	Lð 	Lús#   �B+šABÁ2$B+ÂB(	Â$B+Â+B4rv   c                 ó–   — | j                   5  || j                  v r
	 ddd«       yd| j                  |<   	 ddd«       y# 1 sw Y   yxY w)a2  Record *thread_id* in memory; ``True`` when a persist is still owed.

        The in-memory half stays synchronous even for :meth:`mark_async` so that
        a ``thread_id in tracker`` check immediately after marking is correct --
        the mention-gating logic in the adapters depends on that.
        NFT©re   ri   ©r   rv   s     r   Ú	_rememberz$ThreadParticipationTracker._remember�   sL   € ð �Z‰Zñ 	Ø˜DŸM™MÑ)Ø÷	ð 	ð (,ˆD�M‰M˜)Ñ$Ø÷		÷ 	ñ 	ús   �?¦?¿Ac                 óH   — | j                  |«      r| j                  «        yy)z³Mark *thread_id* as participated and persist.

        Blocking: ends in ``atomic_json_write`` -> ``os.replace``.  Coroutines
        must use :meth:`mark_async` instead.
        N)r~   rz   r}   s     r   ÚmarkzThreadParticipationTracker.markª   s   € ð �>‰>˜)Ô$Ø�J‰J�Lð %r   c              ƒ   ón   K  — | j                  |«      rt        | j                  «      ƒ d{  –—†  yy7 Œ­w)aÅ  Off-loop form of :meth:`mark`.

        ``_save`` ends in ``atomic_json_write`` -> ``os.replace``, whose
        duration is unbounded under filesystem pressure.  Every caller of this
        tracker sits on an inbound-message coroutine in the Discord and Matrix
        adapters, so the rename must not be paid inline on the event loop -- it
        stalls every other adapter's polling and every in-flight turn for as
        long as it runs.
        N)r~   Ú
_to_threadrz   r}   s     r   Ú
mark_asyncz%ThreadParticipationTracker.mark_async³   s.   è ø€ ð �>‰>˜)Ô$Ü˜TŸZ™ZÓ(×(Ñ(ð %Ø(ús   ‚*5¬3­5c                 ób   — | j                   5  || j                  v cd d d «       S # 1 sw Y   y xY wr   r|   r}   s     r   Ú__contains__z'ThreadParticipationTracker.__contains__À   s*   € Ø�Z‰Zñ 	.Ø §¡Ð-÷	.÷ 	.ò 	.ús   �%¥.c                 óz   — | j                   5  | j                  j                  «        d d d «       y # 1 sw Y   y xY wr   )re   ri   r1   r2   s    r   r1   z ThreadParticipationTracker.clearÄ   s.   € Ø�Z‰Zñ 	"Ø�M‰M×ÑÔ!÷	"÷ 	"ñ 	"ús   �1±:)rY   ©r   N)r3   r4   r5   r6   Ú_MAX_TRACKEDr9   r7   r   r   rm   rt   rh   rz   r:   r~   r€   rƒ   r…   r1   r   r   r   rX   rX   q   sœ   „ ñYð €LñU cð U¸ó Uð$D˜Tó DðX�t˜C‘yó XóLð 3ð ¨4ó ð˜cð  dó ð)¨#ð )°$ó )ð. cð .¨dó .ô"r   rX   Úphonec                 ó|   — | syt        | «      dk  rt        | «      dkD  r| dd dz   | dd z   S dS | dd dz   | dd z   S )	zFRedact a phone number for logging, preserving country code and last 4.z<none>é   é   Né   z****éþÿÿÿéüÿÿÿ)r    )r‰   s    r   Úredact_phoner�   É   sZ   € áØÜ
ˆ5ƒz�Q‚Ü25°e³*¸q².ˆu�R�aˆy˜6Ñ! E¨"¨# JÑ.ÐLÀfÐLØ��!ˆ9�vÑ  b c 
Ñ*Ð*r   z0^\s*\|?\s*:?-+:?\s*(?:\|\s*:?-+:?\s*){1,}\|?\s*$Úlinec                 ó&   — d| j                  «       v S )z:Return True if *line* could plausibly be a table data row.Ú|)rS   )r‘   s    r   Úis_table_rowr”   Ù   s   € à�$—*‘*“,ÐÐr   c                 ó   — ddl m}  || «      S )zOSplit a GFM table row into stripped cells (delegates to agent.markdown_tables).r   )Úsplit_table_row)Úagent.markdown_tablesr–   )r‘   r–   s     r   Úsplit_markdown_table_rowr˜   Þ   s   € å5Ù˜4Ó Ð r   Útable_blockc           	      ó‚  — t        | «      dk\  rt        | d   «      ng }t        |«      dk  rdj                  | «      S t        t        | d   «      «      t        |«      dz   k(  }g }t        | dd d¬«      D ]±  \  }}t        |«      }|r|r
|d   r|d   nd|› �}|dd }nt	        d	„ |D «       d|› �«      }|}|d
gt        |«      z  z   dt        |«       }t        ||«      D �	�
cg c]  \  }	}
|s|
|k7  rd|	› d|
› �‘Œ }}	}
|j                  dj                  d|› d�g|¢«      «       Œ³ dj                  |«      S c c}
}	w )z¸Render a GFM table as bold-heading + bullet groups (same alignment logic as Telegram's
    renderer: without a row-label column the full row is data and the heading bullet is skipped).é   r   r�   Ú
r   N)ÚstartzRow c              3   ó&   K  — | ]	  }|sŒ|–— Œ y ­wr   r   )r^   Úcells     r   r`   z&_render_table_block.<locals>.<genexpr>ò   s   è ø€ Ò; T²dœDÑ;ùs   ‚ŠrM   u   â€¢ z: z**rN   )r    r˜   ÚjoinÚ	enumeraterJ   ÚzipÚappend)r™   ÚheadersÚhas_row_label_colÚrendered_groupsÚindexÚrowÚcellsÚheadingÚ
data_cellsÚheaderrG   Úbulletss               r   Ú_render_table_blockr®   ä   sr  € ô ;>¸kÓ:JÈaÒ:OÔ& {°1¡~Ô6ÐUW€GÜ
ˆ7ƒ|�aÒØ�y‰y˜Ó%Ð%ÜÔ4°[À±^ÓDÓEÌÈWËÐXYÑIYÑYÐØ!#€OÜ ¨A¨B °qÔ9ò H‰
ˆˆsÜ(¨Ó-ˆÙÙ"'¨E°!ªH�e˜A’h¸DÀÀ¸.ˆGØ˜q˜r˜‰JäÑ;¨UÔ;¸tÀEÀ7¸^ÓLˆGØˆJØ  B 4¬#¨g«,Ñ#6Ñ6¸¼#¸g»,ÐGˆ
ÜADÀWÈjÓAY÷ =±°¸Ù'¨5°GÒ+;ð ˜&˜  E 7Ò+ð =ˆñ =à×Ñ˜tŸy™y¨B¨w¨i°rÐ*:Ð)E¸WÐ)EÓFÕGðHð �;‰;�Ó'Ð'ùó=s   Ã%D;c                 óL  — d| vsd| vr| S | j                  d«      }g }d}d}|t        |«      k  rå||   }|j                  «       j                  d«      }||z  }|s•|s“d|v r�|dz   t        |«      k  r~t        j                  ||dz      «      rc|dz   }|t        |«      k  r0t        ||   «      r"|dz  }|t        |«      k  rt        ||   «      rŒ"|j                  t        ||| «      «       |}n|j                  |«       |dz  }|t        |«      k  rŒådj                  |«      S )	zURewrite GFM pipe tables into bold-heading + bullet groups; fenced code is left alone.r“   Ú-rœ   Fr   ú```r   r�   )
Úsplitr    ÚlstripÚ
startswithÚTABLE_SEPARATOR_REÚmatchr”   r£   r®   r    )rO   ÚlinesÚoutÚin_fenceÚir‘   Úis_fence_lineÚjs           r   Úconvert_table_to_bulletsr½   û   s$  € à
�$�˜# T™/ØˆØ�J‰J�tÓ€EØ€CØ€HØ	€AØ
Œc�%‹jŠ.Ø�Q‰xˆØŸ™›×0Ñ0°Ó7ˆØ�MÑ!ˆÙ™M¨s°d©{¸qÀ1¹uÄsÈ5ÃzÒ?QÜ&×,Ñ,¨U°1°q±5©\Ô:Ø�A‘ˆAØ”c˜%“j’.¤\°%¸±(Ô%;Ø�Q‘�ð ”c˜%“j’.¤\°%¸±(Õ%;à�J‰JÔ*¨5°°1¨:Ó6Ô7Ø‰Aà�J‰J�tÔØ�‰FˆAð Œc�%‹j‹.ð �9‰9�S‹>Ðr   )Úplatform_labelÚdisplay_labelÚdefaultsÚlogger_Ú
log_prefixr¾   r¿   rÀ   zlist[str] | NonerÁ   zlogging.Logger | Nonezlist[re.Pattern]c          	      ó†  ‡‡— |xs t         Šˆˆfd„}|�³|xs |}| €g S t        | t        «      r| gn| }t        |t        «      s)‰j	                  d‰|t        |«      j                  «       g S  ||D �	cg c]&  }	t        |	t        «      sŒ|	j                  «       sŒ%|	‘Œ( c}	d|«      }
|
r‰j                  d‰t        |
«      |«       |
S | €t        |xs g «      }n¦t        | t        «      r�| j                  «       }	 |rt        j                  |«      ng }t        |t        «      r|nC|j                  «       D ��cg c](  }|j                  d«      D ]  }|j                  «       ‘Œ Œ* c}}}nt        | t        «      r| n| g}d„ |D «       D �cg c]  }|sŒ|‘Œ	 }} ||d«      S c c}	w # t        $ r d}Y Œ w xY wc c}}w c c}w )	a9  Compile regex wake-word/mention patterns from config or env values.

    * **Config-style** (dingtalk, telegram): pass ``platform_label``. ``raw`` must be a
      list or string (else warn + ``[]``); non-string entries skipped; info log on load.
    * **Wakeword-style** (photon, bluebubbles): pass ``defaults``. ``raw`` may be None
      (defaults), a string (JSON list or comma/newline separated), a list, or a scalar.
    ``log_prefix`` is interpolated into every log line so per-adapter output stays
    byte-identical to the historical inline implementations.
    c                 óò   •— g }| D ]6  }	 |j                  t        j                  |t        j                  «      «       Œ8 |S # t        j                  $ r"} ‰j
                  |‰g|¢|‘|‘­Ž  Y d }~Œkd }~ww xY wr   )r£   ÚreÚcompileÚ
IGNORECASEÚerrorÚwarning)ÚpatternsÚwarn_fmtÚ	warn_argsÚcompiledrT   ÚexcÚlogrÂ   s         €€r   Ú_compilez*compile_mention_patterns.<locals>._compile"  s{   ø€ Ø%'ˆØò 	LˆGðLØ—‘¤§
¡
¨7´B·M±MÓ BÕCð	Lð
 ˆøô —8‘8ò LØ�—‘˜H jÐK°9ÐK¸gÐKÀs×KÐKûðLús   Š3AÁA6ÁA1Á1A6Nz9[%s] %s mention_patterns must be a list or string; got %sz&[%s] Invalid %s mention pattern %r: %sz$[%s] Loaded %d %s mention pattern(s)ú,c              3   óN   K  — | ]  }t        |«      j                  «       –— Œ y ­wr   )r9   rS   )r^   Úps     r   r`   z+compile_mention_patterns.<locals>.<genexpr>E  s   è ø€ Ò:¨Aœ˜Q›Ÿ™ŸÑ:ùs   ‚#%z#[%s] Invalid mention pattern %r: %s)Úloggerrs   r9   rt   rÉ   Útyper3   rS   Úinfor    rp   rq   rD   Ú
splitlinesr²   )ÚrawrÂ   r¾   r¿   rÀ   rÁ   rÐ   ÚdisplayrÊ   rÓ   rÍ   rO   Úloadedr‘   Úpartr_   ÚtextsrÏ   s    `               @r   Úcompile_mention_patternsrÝ     s°  ù€ ð Ò
”V€Cõð Ð!ØÒ1 >ˆØˆ;ØˆIÜ& s¬CÔ0�C‘5°cˆÜ˜(¤DÔ)Ø�K‰KÐSØ" N´D¸³N×4KÑ4KôMàˆIÙ¨ÖU 1´J¸qÄ#Õ4FÈ1Ï7É7Í9šQÒUØDÀgóOˆáØ�H‰HÐ;¸ZÌÈXËÐX_Ô`ØˆØ
€{Ü˜š BÓ'‰Ü	�CœÔ	Ø�y‰y‹{ˆð	Ù)-”T—Z‘Z Ô%°2ˆFô (¨´Ô5‘6Ø%)§_¡_Ó%6÷<TØ!À4Ç:Á:ÈcÃ?ò<TØ;?ˆD�J‰J�Lð<TØó<T‰ô % S¬$Ô/‘3°c°UˆÙ:°Ô:Ö@�1ºaŠQÐ@€EÐ@Ù�EÐ@ÓAÐAùò% Vøô ò 	ØŠFð	üó<Tùò As6   Á3F"Â	F"ÂF"Ã;F' Ä9-F8ÆF>ÆF>Æ'F5Æ4F5c                 óP   — t        d„ | j                  d«      D «       «      dz  dk(  S )z?Return True when *text* ends inside an unclosed ``` code fence.c              3   ó>   K  — | ]  }|j                  d «      –— Œ y­w)r±   N)r´   )r^   r‘   s     r   r`   z*text_has_unclosed_fence.<locals>.<genexpr>P  s   è ø€ ÒC¨$ˆt�‰˜u×%ÑCùs   ‚rœ   r�   r   )Úsumr²   ©rO   s    r   Útext_has_unclosed_fencerâ   N  s&   € äÑC°$·*±*¸TÓ2BÔCÓCÀaÑGÈ1ÑLÐLr   c                 óX   — t        | j                  «       j                  d«      d   «      S )z=True when the last non-empty line starts and ends with ``|``.rœ   éÿÿÿÿ)Ú_is_pipe_rowÚrstripr²   rá   s    r   Útext_ends_with_table_rowrç   S  s#   € ä˜Ÿ™›×+Ñ+¨DÓ1°"Ñ5Ó6Ð6r   c                 ó@   — | j                  «       j                  d«      S )z<True when an atomic block is a code block (starts with ```).r±   )r³   r´   rá   s    r   Úis_fence_atomré   X  s   € à�;‰;‹=×#Ñ# EÓ*Ð*r   c                 ój   — | j                  «       }|j                  d«      xr |j                  d«      S )Nr“   )rS   r´   Úendswith)r‘   Ústrippeds     r   rå   rå   ]  s.   € Ø�z‰z‹|€HØ×Ñ˜sÓ#Ò>¨×(9Ñ(9¸#Ó(>Ð>r   c                 ó<   — t        | j                  d«      d   «      S )z?True when an atomic block is a table (first line is ``|...|``).rœ   r   )rå   r²   rá   s    r   Úis_table_atomrî   b  s   € ä˜Ÿ
™
 4Ó(¨Ñ+Ó,Ð,r   u   [ã€‚ï¼�ï¼Ÿ.!?]\nc                 ó6   — |t         u r|S ddlm}  || ||«      S )z¦Code-point count of the longest prefix of *text* within *budget* ``len_fn`` units
    (callers guarantee ``len_fn(text) > budget``, so plain ``len`` needs no search).r   )Ú_custom_unit_to_cp)r    Úgateway.platforms.baserð   )rO   ÚbudgetÚlen_fnrð   s       r   Ú
_cp_budgetrô   j  s"   € ð ”�}ØˆÝ9Ù˜d F¨FÓ3Ð3r   c                 ój  — |xs t         } || «      |k  r| dfS | dt        | ||«       }|j                  d«      }t        j	                  |«      D �cg c]  }|j                  «       ‘Œ }}|dkD  r|dz   n|r|d   nd}|s&|j                  d«      }|dkD  r|dz   n
t        |«      }| d| | |d fS c c}w )	uM  Split at the nearest paragraph boundary within *max_chars*; return (head, tail).

    Priority: blank line â†’ newline after sentence-ending punctuation (CJK/ASCII)
    â†’ last newline â†’ forced split at the window boundary. ``head + tail == text``
    always holds. *len_fn* measures in custom units (e.g. UTF-16 code units).
    rM   NrN   r   r�   rä   rœ   r   )r    rô   ÚrfindÚ_SENTENCE_END_NEWLINE_REÚfinditerÚend)	rO   Ú	max_charsró   Ú_lenÚwindowÚposÚmÚsentence_endsÚcuts	            r   Úsplit_at_paragraph_boundaryr  s  sÇ   € ð Š=”S€DÙˆDƒz�YÒØ�RˆxˆØÐ4”:˜d I¨tÓ4Ð5€FØ
�,‰,�vÓ
€CÜ&>×&GÑ&GÈÓ&OÖP �Q—U‘U•WÐP€MÐPØ˜1’Wˆ#�Š'¹ =°Ò#4È1€CÙØ�l‰l˜4Ó ˆØ˜qšˆc�AŠg¤c¨&£kˆØ��ˆ:�t˜C˜D�zÐ!Ð!ùò Qs   ÁB0ú	list[str]c                 ó²  ‡‡— g Šg Šd}dˆˆfd„}| j                  d«      D ]¬  }|r-‰j                  |«       |j                  d«      sŒ(d} |«        Œ2|j                  d«      r |«        d}‰j                  |«       Œ^|j                  «       dk(  r |«        Œy‰r!t	        ‰d   «      t	        |«      k7  r |«        ‰j                  |«       Œ®  |«        ‰S )	z’Split markdown into indivisible atoms: fenced code blocks, tables
    (consecutive ``|...|`` lines) and paragraphs. Blank lines belong to no atom.Fc                  ó�   •— ‰rCdj                  ‰«      } | j                  «       r‰j                  | «       ‰j                  «        y y )Nrœ   )r    rS   r£   r1   )ÚatomÚatomsÚcurrent_liness    €€r   Ú_flush_currentz,split_markdown_atoms.<locals>._flush_currentŽ  s:   ø€ ÙØ—9‘9˜]Ó+ˆDØ�z‰zŒ|Ø—‘˜TÔ"Ø×ÑÕ!ð	 r   rœ   r±   TrM   rä   r‡   )r²   r£   r´   rS   rå   )rO   r¹   r  r‘   r  r  s       @@r   Úsplit_markdown_atomsr	  ‡  sÑ   ù€ ð €EØ!#€MØ€Hö"ð —
‘
˜4Ó ò 'ˆÙØ× Ñ  Ô&Ø�‰˜uÕ%Ø �ÙÕ Ø�_‰_˜UÔ#ÙÔØˆHØ× Ñ  Õ&Ø�Z‰Z‹\˜RÒÙÕñ ¤¨m¸BÑ.?Ó!@ÄLÐQUÓDVÒ!VÙÔ Ø× Ñ  Õ&ð!'ñ" ÔØ€Lr   Ú
prev_chunkÚ
next_chunkc                 óà   — | j                  «       }|j                  «       }|j                  d«      s|j                  d«      ryt	        | «      r |rt        |j                  d«      d   «      ryy)zV``'\n'`` when the boundary sits at a code fence or a continued table, else ``'\n\n'``.r±   rœ   r   rN   )ræ   r³   rë   r´   rç   rå   r²   )r
  r  Úprev_trimmedÚnext_trimmeds       r   Úinfer_block_separatorr  ª  se   € à×$Ñ$Ó&€LØ×$Ñ$Ó&€LØ×Ñ˜UÔ# |×'>Ñ'>¸uÔ'EØÜ 
Ô+±ÄÈl×N`ÑN`ÐaeÓNfÐghÑNiÔAjØØr   c                 ó6  — g }d}|t        | «      k  r†| |   }t        |«      rQ|dz   t        | «      k  r@|t        || |dz      «      z   | |dz      z   }|dz  }t        |«      r|dz   t        | «      k  rŒ@|j                  |«       |dz  }|t        | «      k  rŒ†|S )zžRejoin chunks truncated mid-fence: while chunk *i* has an unclosed fence
    and a successor exists, merge the successor in via :func:`infer_block_separator`.r   r   )r    râ   r  r£   )ÚchunksÚresultrº   Úcurrents       r   Úmerge_streaming_fencesr  µ  s²   € ð €FØ	€AØ
Œc�&‹kŠ/Ø˜‘)ˆÜ% gÔ.°1°q±5¼3¸v»;Ò3FØÔ 5°g¸vÀaÈ!Áe¹}Ó MÑMÐPVÐWXÐ[\ÑW\ÑP]Ñ]ˆGØ�‰FˆAô & gÔ.°1°q±5¼3¸v»;Ó3Fð 	�‰�gÔØ	ˆQ‰ˆð Œc�&‹k‹/ð €Mr   c                 ó¼   — t        | «      dk  r| S g }d}| D ]B  }|�d|› d|› �n|}t        ||du|xs d«      \  }}|r|nd}|j                  |r|dz   n|«       ŒD |S )z•Close orphaned ``` fences at each chunk boundary and reopen (with the
    original language tag) on the next, so every chunk is fence-balanced alone.r   Nr±   rœ   rM   z
```)r    Úfence_state_afterr£   )r  r¸   Ú
carry_langÚchunkÚbodyÚin_codeÚlangs          r   Úbalance_fences_across_chunksr  Ä  sˆ   € ô ˆ6ƒ{�aÒØˆØ€CØ€JØò 8ˆØ.8Ð.D��Z�L  5 'Ñ*È%ˆÜ)¨%°À4Ð1GÈÒIYÐWYÓZ‰ˆ�Ù$‘T¨$ˆ
Ø�
‰
¡W�4˜'’>°$Õ7ð	8ð
 €Jr   Fr  r  ztuple[bool, str]c                 óÄ   — | j                  d«      D ]I  }|j                  «       }|j                  d«      sŒ%|dd j                  «       }|rdn
d|r|d   ndf\  }}ŒK ||fS )	zSWalk ``text`` line by line toggling on ``` lines; return the final (in_code, lang).rœ   r±   r›   N©FrM   Tr   rM   )r²   rS   r´   )rO   r  r  r‘   rì   Útags         r   r  r  Ó  sp   € à—
‘
˜4Ó ò VˆØ—:‘:“<ˆØ×Ñ˜uÕ%Ø˜1˜2�,×$Ñ$Ó&ˆCÙ+2™K¸É¸sÀ1ºvÐRTÐ8U‰MˆG‘Tð	Vð
 �Dˆ=Ðr   c                 ó&  — |xs t         }g }d}| D ]j  }|s|n|› |› |› �}	 ||	«      |k  r|	}Œ|r|j                  |«       d} ||«      |k  r|}Œ@|�|j                   ||«      «       ŒZ|j                  |«       Œl |r|j                  |«       |S )z¯Greedily pack *blocks* (joined with *sep*) into chunks of at most *max_length*; an
    oversized block goes through *overflow(block)* (-> list of chunks) if given, else as-is.rM   )r    r£   Úextend)
ÚblocksÚ
max_lengthró   ÚsepÚoverflowrû   Úpackedr  ÚblockÚ	candidates
             r   Úgreedy_pack_blocksr)  Ý  s­   € ð Š=”S€DØ€FØ€GØò !ˆÙ!(‘E°°	¸#¸¸u¸gÐ.Fˆ	Ù�	‹?˜jÒ(ØˆGØÙØ�M‰M˜'Ô"ØˆGÙ�‹;˜*Ò$Ø‰GØÐ!Ø�M‰M™( 5›/Õ*à�M‰M˜%Õ ð!ñ Ø�‰�gÔØ€Mr   T)Úprefer_paragraphsÚbalance_fencesc                ór   — | sg S |rt        | ||«      }nt        | ||xs t        «      }|rt        |«      S |S )aE  Split markdown into chunks of at most *limit*, respecting fences.

    ``prefer_paragraphs=True`` (yuanbao-derived): atoms (fences, tables, paragraphs) are
    greedily merged, oversized non-atomic chunks split at paragraph boundaries, small
    neighbours re-merged; a single atom larger than *limit* is emitted oversize, not broken.
    ``prefer_paragraphs=False`` (stream_consumer-derived): newline-preferred hard splitting
    with headroom reserved for fence markers. ``balance_fences=True`` closes/reopens fences
    at chunk boundaries so each chunk renders standalone.
    )Ú_chunk_markdown_paragraphsÚ_chunk_newline_preferredr    r  )rO   Úlimitró   r*  r+  r  s         r   Úsplit_text_fence_awarer0  ö  sB   € ñ Øˆ	ÙÜ+¨D°%¸Ó@‰ä)¨$°°v²}ÄÓEˆÙ3AÔ'¨Ó/ÐMÀvÐMr   c                 óà  — |xs t         } || «      |k  r| gS g }t        «       }g }d}t        | «      D ]¥  } ||«      }	|rdnd}
||
z   |	z   |kD  r(|r&|j                  dj	                  |«      «       g dd}
}}|sG|	|kD  rBt        |«      st        |«      r,|j                  t        |«      «       |j                  |«       Œ�|j                  |«       ||
|	z   z  }Œ§ |r |j                  dj	                  |«      «       g }t        |«      D ]Ž  \  }} ||«      |k  s||v st        |«      r|j                  |«       Œ2|} ||«      |kD  r<t        |||¬«      \  }}|s
|d| ||d }}|r|j                  |«        ||«      |kD  rŒ<|sŒ~|j                  |«       Œ� |dd }|dd D ]/  }|d   dz   |z   } ||«      |k  r||d<   Œ|j                  |«       Œ1 |D �cg c]  }|sŒ|‘Œ	 c}S c c}w )zCYuanbao-derived paragraph/atom chunking pipeline (see module docs).r   r�   rN   )ró   Nr   rä   )r    Úsetr	  r£   r    ré   rî   Úaddr¡   râ   r  )rO   rú   ró   rû   r  Úindivisible_setÚcurrent_partsÚcurrent_lenr  Úatom_lenÚsep_lenr  Úidxr  Ú	remainingÚheadÚmergedÚcombinedÚcs                      r   r-  r-  
  s#  € àŠ=”S€DÙˆDƒz�YÒØˆvˆà€FÜ"%£%€OØ!#€MØ€KÜ$ TÓ*ò *ˆÙ˜“:ˆÙ$‘!¨!ˆØ˜Ñ  8Ñ+¨iÒ7¹MØ�M‰M˜&Ÿ+™+ mÓ4Ô5Ø24°a¸¨˜;ˆMÙ (¨YÒ"6Ü" 4Ô(¬M¸$Ô,?Ø×Ñ¤ F£Ô,Ø�M‰M˜$ÔØØ×Ñ˜TÔ"Ø�w Ñ)Ñ)‰ð*ñ Ø�‰�f—k‘k -Ó0Ô1à€FÜ Ó'ò %‰
ˆˆUÙ�‹;˜)Ò# s¨oÑ'=ÔAXÐY^ÔA_Ø�M‰M˜%Ô ØØˆ	Ù�9‹o 	Ò)Ü9¸)ÀYÐW]Ô^‰OˆD�)ÙØ"+¨J¨YÐ"7¸À9À:Ð9N�i�ÙØ—‘˜dÔ#ñ �9‹o 	Ó)ò Ø�M‰M˜)Õ$ð%ð !  !˜*€FØ˜˜�ò !ˆØ˜"‘: Ñ&¨Ñ.ˆÙ�‹>˜YÒ&Ø!ˆF�2ŠJà�M‰M˜%Õ ð!ð Ö#�!¢ŠAÒ#Ð#ùÒ#s   ÇG+Ç$G+c                 óT  —  || «      |k  r| gS d| v rt        |dz
  |dz  d«      n|}g }| } ||«      |kD  r^t        |||«      }|j                  dd|«      }||dz  k  r|}|j                  |d| «       ||d j	                  d«      } ||«      |kD  rŒ^|r|j                  |«       |S )zCStream-consumer-derived newline-preferred splitting (no balancing).r±   é   r�   r   rœ   r   N)Úmaxrô   rö   r£   r³   )rO   r/  ró   Úsplit_limitr  r:  rò   Úsplit_ats           r   r.  r.  =  sÊ   € áˆdƒ|�uÒØˆvˆà49¸T±M”#�e˜b‘j %¨1¡*¨aÔ0Àu€KØ€FØ€IÙ
�Ó
˜kÒ
)Ü˜I {°FÓ;ˆØ—?‘? 4¨¨FÓ3ˆØ�f ‘kÒ!ØˆHØ�‰�i 	 Ð*Ô+Ø˜h˜iÐ(×/Ñ/°Ó5ˆ	ñ �Ó
˜kÓ
)ñ Ø�‰�iÔ Ø€Mr   i   Ú___hidden___c                 ó®   — 	 | j                   j                  }t        |t        «      r
|t
        z  ryt        | dd«      t        k(  S # t        $ r d}Y Œ;w xY w)uÆ  True when a discord.py channel object is an obfuscated placeholder.

    Skip these wherever guild channels are enumerated (channel directory, message
    backfill): history reads and sends always fail with a permission error. Flag
    first; the sentinel name is the fallback for discord.py builds that don't
    expose the flag (a visible channel literally named ``___hidden___`` is then
    also skipped â€” deliberate bias toward hiding). #90154
    NTÚname)ÚflagsrG   ÚAttributeErrorrs   r7   ÚDISCORD_CHANNEL_OBFUSCATED_FLAGÚgetattrÚDISCORD_OBFUSCATED_CHANNEL_NAME)ÚchannelÚ
flag_values     r   Úis_discord_channel_obfuscatedrN  Y  sX   € ðØ—]‘]×(Ñ(ˆ
ô �*œcÔ" zÔ4SÒ'SØÜ�7˜F DÓ)Ô-LÑLÐLøô	 ò ØŠ
ðús   ‚A ÁAÁA)ÚDict)ÚTYPE_CHECKINGc                   óf   — e Zd ZdZddddœdededefd	„Zd
efd„Zde	de
d
dfd„Zde
d
dfd„Zdd„Zy)ÚTextBatchAggregatora@  Aggregates rapid-fire text events into single messages.

    Replaces the ``_enqueue_text_event`` / ``_flush_text_batch`` pattern
    previously duplicated in telegram, discord, matrix, wecom, and feishu.

    Usage::

        self._text_batcher = TextBatchAggregator(
            handler=self._message_handler,
            batch_delay=0.6,
            split_threshold=1900,
        )

        # In message dispatch:
        if msg_type == MessageType.TEXT and self._text_batcher.is_enabled():
            self._text_batcher.enqueue(event, session_key)
            return
    g333333ã?g       @i   )Úbatch_delayÚsplit_delayÚsplit_thresholdrS  rT  rU  c                óX   — || _         || _        || _        || _        i | _        i | _        y r   )Ú_handlerÚ_batch_delayÚ_split_delayÚ_split_thresholdÚ_pendingÚ_pending_tasks)r   ÚhandlerrS  rT  rU  s        r   r   zTextBatchAggregator.__init__ˆ  s2   € ð  ˆŒØ'ˆÔØ'ˆÔØ /ˆÔØ13ˆŒØ79ˆÕr   r   c                 ó    — | j                   dkD  S )z.Return True if batching is active (delay > 0).r   )rX  r2   s    r   Ú
is_enabledzTextBatchAggregator.is_enabled—  s   € à× Ñ  1Ñ$Ð$r   Úeventr   Nc                 óÈ  — t        |j                  xs d«      }| j                  j                  |«      }|s||_        || j                  |<   n'|j                  › d|j                  › �|_        ||_        | j
                  j                  |«      }|r |j                  «       s|j                  «        t        j                  | j                  |«      «      | j
                  |<   y)z+Add *event* to the pending batch for *key*.rM   rœ   N)r    rO   r[  r*   Ú_last_chunk_lenr\  r=   r>   r?   Úcreate_taskÚ_flush)r   r`  r   Ú	chunk_lenÚexistingÚpriors         r   ÚenqueuezTextBatchAggregator.enqueue›  s´   € ä˜Ÿ
™
Ò( bÓ)ˆ	Ø—=‘=×$Ñ$ SÓ)ˆÙØ$-ˆEÔ!Ø!&ˆD�M‰M˜#Òà'Ÿ}™}˜o¨R°·
±
¨|Ð<ˆHŒMØ'0ˆHÔ$ð ×#Ñ#×'Ñ'¨Ó,ˆÙ˜Ÿ™œØ�L‰LŒNÜ#*×#6Ñ#6°t·{±{À3Ó7GÓ#Hˆ×Ñ˜CÒ r   c              ƒ   óX  K  — | j                   j                  |«      }| j                  j                  |«      }|rt        |dd«      nd}|| j                  k\  r| j
                  n| j                  }t        j                  |«      ƒ d{  –—†  | j                  j                  |d«      }|r	 | j                  |«      ƒ d{  –—†  | j                   j                  |«      |u r| j                   j                  |d«       yy7 Œw7 ŒA# t        $ r t        j                  d|«       Y Œ`w xY w­w)z/Wait then dispatch the batched event for *key*.rb  r   Nz<[TextBatchAggregator] Error dispatching batched event for %s)r\  r*   r[  rJ  rZ  rY  rX  r?   Úsleepr.   rW  rD   rÔ   Ú	exception)r   r   r@   ÚpendingÚlast_lenÚdelayr`  s          r   rd  zTextBatchAggregator._flush¬  s  è ø€ à×*Ñ*×.Ñ.¨sÓ3ˆØ—-‘-×#Ñ# CÓ(ˆÙ=D”7˜7Ð$5°qÔ9È!ˆð &.°×1FÑ1FÒ%F�×!Ò!ÈD×L]ÑL]ˆÜ�m‰m˜EÓ"×"Ð"à—‘×!Ñ! # tÓ,ˆÙðfØ—m‘m EÓ*×*Ð*ð ×Ñ×"Ñ" 3Ó'¨<Ñ7Ø×Ñ×#Ñ# C¨Õ.ð 8ð 	#øð
 +ùÜò fÜ× Ñ Ð!_ÐadÖeðfüsH   ‚BD*Â	DÂ
"D*Â-D ÃDÃD Ã<D*ÄD ÄD'Ä$D*Ä&D'Ä'D*c                 óì   — | j                   j                  «       D ]#  }|j                  «       rŒ|j                  «        Œ% | j                   j	                  «        | j
                  j	                  «        y)zCancel all pending flush tasks.N)r\  Úvaluesr=   r>   r1   r[  )r   r;   s     r   Ú
cancel_allzTextBatchAggregator.cancel_allÀ  sV   € à×'Ñ'×.Ñ.Ó0ò 	ˆDØ—9‘9•;Ø—‘•ð	ð 	×Ñ×!Ñ!Ô#Ø�‰×ÑÕr   r‡   )r3   r4   r5   r6   r8   r7   r   r:   r_  r   r9   rh  rd  rq  r   r   r   rR  rR  t  sw   „ ñð. !Ø Ø#ò:ð ð	:ð
 ð:ð ó:ð%˜Dó %ðI˜\ð I°ð I¸ó Ið"/ ð /¨ó /ô(r   rR  r   )r  r  r   r  r  )NrN   N)Cr6   r?   rA   rp   ÚloggingrÅ   rc   r   Úpathlibr   Útypingr   r   r   Úgateway.platforms.eventr   Úutilsr   Ú	getLoggerr3   rÔ   r
   Ú	to_threadr‚   ÚTaskrE   r9   r7   rL   rÆ   ÚDOTALLÚ	MULTILINErQ   rV   rX   r�   rµ   r:   r”   rt   r˜   r®   r½   rÝ   râ   rç   ré   rå   rî   r÷   rô   r  r	  r  r  r  r  r)  r0  r-  r.  rI  rK  rN  rO  rP  rR  r   r   r   ú<module>r|     sž  ðñ3ó Û Û Û Û 	Û Û Ý ß 0Ñ 0Ý 0Ý #à	ˆ×	Ñ	˜8Ó	$€÷)ñ )ð^ ×Ñ€
ð	˜H W§\¡\Ñ2ð 	°tó 	ð%�~ c¨3 hÑ/ð %°cð %À#ð %ÈCð %ÐTXó %ð €R‡Z�ZÐ  "§)¡)Ó,¨eÐ4Ø€R‡Z�Z�˜bŸi™iÓ(¨%Ð0Ø€R‡Z�ZÐ3°R·Y±YÓ?ÀÐGØ€R‡Z�ZÐ1°2·9±9Ó=¸uÐEØ€R‡Z�ZÐ(Ó)¨2Ð.Ø€R‡Z�Z�
Ó˜UÐ#Ø€R‡Z�Z�˜rŸ|™|Ó,¨bÐ1Ø€R‡Z�ZÐ)Ó*¨EÐ2Ø€R‡Z�Z�	Ó˜FÐ#ð
€ð˜ð  ó ÷U"ñ U"ðp+˜ð + ó +ð  �R—Z‘ZÐ SÓTÐ ð�sð ˜tó ð
! 3ð !¨4°©9ó !ð( T¨#¡Yð (°3ó (ð. 3ð ¨3ó ð2 TXØ9=Ð^bØ@Dò2B°ð 2BÀcÈDÁjð 2BØ,/°$©Jð2BØI[ð2Bà&=ð2BàI[ó2BðtM #ð M¨$ó Mð
7 3ð 7¨4ó 7ð
+˜ð + ó +ð
?�sð ?˜tó ?ð
-˜ð - ó -ð
 &˜2Ÿ:™:Ð&9Ó:Ð ò4ó"ð( ˜sð   {ó  ðF cð °sð ¸só óóñ˜Cð ¨$ð ¸cð ÐK]ó óð2NÈ$Ø*/ôNó(0$òfð0 #*Ð Ø"0Ð ðM¨dó Mõ, Ý  Û Û ÷Rò Rr   