
    Pmjd}                        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mZ ddlm	Z	m
Z
 ddlmZ e	rddlmZ  ej        e          Z G d d          Z G d	 d
          Z ej        dej                  Z ej        dej                  Z ej        dej                  Z ej        dej                  Z ej        d          Z ej        d          Z ej        dej                  Z ej        d          Z ej        d          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'd(d)dd*fd+Z+de de%fd,Z,de de%fd-Z-de de%fd.Z.de de%fd/Z/ ej        d0          Z0dBd1Z1de dd2fd3Z2d4e d5e de fd6Z3dCd8Z4dCd9Z5dDd;Z6	 dBd<d=d>d?Z7dBd@Z8dA Z9dS )EzShared helper classes for gateway platform adapters.

Extracts common patterns that were duplicated across 5-7 adapters:
message deduplication, text batch aggregation, markdown stripping,
and thread participation tracking.
    N)Path)TYPE_CHECKINGDict)atomic_json_write)MessageEventc                   Z    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dS )MessageDeduplicatora|  TTL-based message deduplication cache.

    Replaces the identical ``_seen_messages`` / ``_is_duplicate()`` pattern
    previously duplicated in discord, slack, dingtalk, wecom, weixin,
    mattermost, and feishu adapters.

    Usage::

        self._dedup = MessageDeduplicator()

        # In message handler:
        if self._dedup.is_duplicate(msg_id):
            return
      ,  max_sizettl_secondsc                 0    i | _         || _        || _        d S N)_seen	_max_size_ttl)selfr   r   s      ?/home/thesage/.hermes/hermes-agent/gateway/platforms/helpers.py__init__zMessageDeduplicator.__init__+   s    ')
!			    msg_idreturnc                    |sdS t          j                     }|| j        v r#|| j        |         z
  | j        k     rdS | j        |= || j        |<   t          | j                  | j        k    r|| j        z
  fd| j                                        D             | _        t          | j                  | j        k    rKt          | j                                        d           | j         d         }t          |          | _        dS )z?Return True if *msg_id* was already seen within the TTL window.FTc                 (    i | ]\  }}|k    ||S  r   ).0kvcutoffs      r   
<dictcomp>z4MessageDeduplicator.is_duplicate.<locals>.<dictcomp>=   s$    LLL41aV!Qr   c                     | d         S )N   r   )items    r   <lambda>z2MessageDeduplicator.is_duplicate.<locals>.<lambda>D   s
    T!W r   )keyN)timer   r   lenr   itemssorteddict)r   r   nownewestr   s       @r   is_duplicatez MessageDeduplicator.is_duplicate0   s    	5ikkTZTZ''$)33t
6" 
6tz??T^++49_FLLLL4:+;+;+=+=LLLDJ4://  J$$&&,,   >/""$ "&\\
ur   c                     |sdS | j                             |          }|dS t          j                    |z
  | j        k     rdS | j         |= dS )zBReturn whether *msg_id* is live in the cache without inserting it.FNT)r   getr&   r   )r   r   seen_ats      r   containszMessageDeduplicator.containsI   sX     	5*..((?59;; 49,,4Jvur   Nc                 <    | j                             |d           dS )z<Release a claimed message ID after cancelled/failed handoff.N)r   pop)r   r   s     r   discardzMessageDeduplicator.discardU   s    
vt$$$$$r   c                 8    | j                                          dS )zClear all tracked messages.N)r   clearr   s    r   r6   zMessageDeduplicator.clearY   s    
r   )r
   r   )__name__
__module____qualname____doc__intfloatr   strboolr-   r1   r4   r6   r   r   r   r	   r	      s              %        
3 4    2
s 
t 
 
 
 
%c %d % % % %    r   r	   c                   h    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dde	d
dfdZ
de	d
dfdZddZdS )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_thresholdrB   rC   rD   c                Z    || _         || _        || _        || _        i | _        i | _        d S r   )_handler_batch_delay_split_delay_split_threshold_pending_pending_tasks)r   handlerrB   rC   rD   s        r   r   zTextBatchAggregator.__init__u   s8      '' /3579r   r   c                     | j         dk    S )z.Return True if batching is active (delay > 0).r   )rG   r7   s    r   
is_enabledzTextBatchAggregator.is_enabled   s     1$$r   eventr   r%   Nc                    t          |j        pd          }| j                            |          }|s||_        || j        |<   n|j         d|j         |_        ||_        | j                            |          }|r(|                                s|                                 t          j	        | 
                    |                    | j        |<   dS )z+Add *event* to the pending batch for *key*. 
N)r'   textrJ   r/   _last_chunk_lenrK   donecancelasynciocreate_task_flush)r   rO   r%   	chunk_lenexistingpriors         r   enqueuezTextBatchAggregator.enqueue   s    
(b))	=$$S)) 	1$-E!!&DM#'}<<
<<HM'0H$ #'',, 	 	LLNNN#*#6t{{37G7G#H#HC   r   c                 >  K   | j                             |          }| j                            |          }|rt          |dd          nd}|| j        k    r| j        n| j        }t          j        |           d{V  | j        	                    |d          }|rH	 | 
                    |           d{V  n+# t          $ r t                              d|           Y nw xY w| j                             |          |u r| j         	                    |d           dS dS )z/Wait then dispatch the batched event for *key*.rT   r   Nz<[TextBatchAggregator] Error dispatching batched event for %s)rK   r/   rJ   getattrrI   rH   rG   rW   sleepr3   rF   	Exceptionlogger	exception)r   r%   current_taskpendinglast_lendelayrO   s          r   rY   zTextBatchAggregator._flush   s]     *..s33-##C((=DK77$5q999! &.1F%F%F!!DL]mE"""""""""!!#t,, 	ffmmE********** f f f  !_adeeeeef ""3''<77##C..... 87s   B9 9%C! C!c                     | j                                         D ]*}|                                s|                                 +| j                                          | j                                         dS )zCancel all pending flush tasks.N)rK   valuesrU   rV   r6   rJ   )r   tasks     r   
cancel_allzTextBatchAggregator.cancel_all   sm    '..00 	 	D99;; !!###r   r   N)r8   r9   r:   r;   r=   r<   r   r?   rN   r>   r]   rY   rk   r   r   r   rA   rA   a   s         . ! #: : : 	:
 : : : : :%D % % % %I^ I# I$ I I I I"/ / / / / /(     r   rA   z\*\*(.+?)\*\*z	\*(.+?)\*z \b__(?![\s_])(.+?)(?<![\s_])__\bz\b_(?![\s_])(.+?)(?<![\s_])_\bz```[a-zA-Z0-9_+-]*\n?z`(.+?)`z
^#{1,6}\s+z\[([^\]]+)\]\([^\)]+\)z\n{3,}rS   r   c                    t                               d|           } t                              d|           } t                              d|           } t                              d|           } t
                              d|           } t                              d|           } t                              d|           } t                              d|           } t                              d|           } | 
                                S )zStrip markdown formatting for plain-text platforms (SMS, iMessage, etc.).

    Replaces the identical ``_strip_markdown()`` functions previously
    duplicated in sms.py, bluebubbles.py, and feishu.py.
    z\1rQ   

)_RE_BOLDsub_RE_ITALIC_STAR_RE_BOLD_UNDER_RE_ITALIC_UNDER_RE_CODE_BLOCK_RE_INLINE_CODE_RE_HEADING_RE_LINK_RE_MULTI_NEWLINEstriprS   s    r   strip_markdownr{      s     <<t$$Dud++DeT**Dt,,Db$''Dud++D??2t$$D<<t$$D  ..D::<<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d	fdZdedefdZddZd	S )ThreadParticipationTrackera  Persistent tracking of threads the bot has participated in.

    Replaces the identical ``_load/_save_participated_threads`` +
    ``_mark_thread_participated`` pattern previously duplicated in
    discord.py and matrix.py.

    Usage::

        self._threads = ThreadParticipationTracker("discord")

        # Check membership:
        if thread_id in self._threads:
            ...

        # Mark participation:
        self._threads.mark(thread_id)
      platform_namemax_trackedc                 h    || _         || _        d |                                 D             | _        d S )Nc                 .    i | ]}t          |          d S r   r>   r   	thread_ids     r   r    z7ThreadParticipationTracker.__init__.<locals>.<dictcomp>   s-     *
 *
 *
%.C	NND*
 *
 *
r   )	_platform_max_tracked_load_threads)r   r   r   s      r   r   z#ThreadParticipationTracker.__init__   s=    &'*
 *
26**,,*
 *
 *
r   r   c                 8    ddl m}  |            | j         dz  S )Nr   )get_hermes_homez_threads.json)hermes_constantsr   r   )r   r   s     r   _state_pathz&ThreadParticipationTracker._state_path   s2    444444  dn#C#C#CCCr   c                    |                                  }|                                r[	 t          j        |                    d                    }t          |t                    rd |D             S n# t          $ r Y nw xY wg S )Nzutf-8)encodingc                 ,    g | ]}t          |          S r   r   r   s     r   
<listcomp>z4ThreadParticipationTracker._load.<locals>.<listcomp>   s    AAAyC	NNAAAr   )r   existsjsonloads	read_text
isinstancelistra   )r   pathdatas      r   r   z ThreadParticipationTracker._load   s    !!;;== 	z$..'."B"BCCdD)) BAADAAAAB   	s   AA4 4
B BNc                    |                                  }t          | j                  }t          |          | j        k    r/|| j         d          }t
                              |          | _        t          ||d            d S )N)indent)r   r   r   r'   r   r*   fromkeysr   )r   r   thread_lists      r   _savez ThreadParticipationTracker._save  s|    !!4=)){d///%t'8&8&9&9:K MM+66DM$D999999r   r   c                 X    || j         vr d| j         |<   |                                  dS dS )z-Mark *thread_id* as participated and persist.N)r   r   r   r   s     r   markzThreadParticipationTracker.mark  s4    DM))'+DM)$JJLLLLL *)r   c                     || j         v S r   )r   r   s     r   __contains__z'ThreadParticipationTracker.__contains__  s    DM))r   c                 8    | j                                          d S r   )r   r6   r7   s    r   r6   z ThreadParticipationTracker.clear  s    r   )r~   rl   )r8   r9   r:   r;   _MAX_TRACKEDr>   r<   r   r   r   r   r   r   r   r?   r   r6   r   r   r   r}   r}      s         $ L
 
c 
 
 
 
 
DT D D D D	tCy 	 	 	 	: : : :c d    *c *d * * * *     r   r}   phonec                     | sdS t          |           dk    r-t          |           dk    r| dd         dz   | dd         z   ndS | dd         dz   | dd         z   S )	zRedact a phone number for logging, preserving country code and last 4.

    Replaces the identical ``_redact_phone()`` functions in signal.py,
    sms.py, and bluebubbles.py.
    z<none>      N   z****)r'   )r   s    r   redact_phoner     ss      x
5zzQ25e**q..uRaRy6!E"##J..fL!9vbcc
**r   z0^\s*\|?\s*:?-+:?\s*(?:\|\s*:?-+:?\s*){1,}\|?\s*$linec                 P    |                                  }t          |          od|v S )z:Return True if *line* could plausibly be a table data row.|)ry   r?   r   strippeds     r   is_table_rowr   7  s$    zz||H>>-cXo-r   c                 $    ddl m}  ||           S )a(  Split a GFM table row into stripped cell values.

    Thin delegate to the canonical implementation in
    :mod:`agent.markdown_tables` (``split_table_row``) so the three
    formerly byte-identical copies (here, ``agent/markdown_tables.py``,
    ``weixin._split_table_row``) share one body.
    r   )split_table_row)agent.markdown_tablesr   )r   r   s     r   split_markdown_table_rowr   =  s&     655555?4   r   table_blockc                 p   t          |           dk     rd                    |           S t          | d                   }t          |          dk     rd                    |           S t          |           dk    rt          | d                   ng }t          |          t          |          dz   k    }g }t          | dd         d          D ]S\  }}t          |          }|r"|r|d         r|d         nd| }|dd         }	nt	          d	 |D             d|           }|}	t          |	          t          |          k     r7|	                    d
gt          |          t          |	          z
  z             n7t          |	          t          |          k    r|	dt          |                   }	g }
t          ||	          D ])\  }}|s||k    r|
                    d| d|            *d| dg|
}|                    d                    |                     Ud                    |          S )u4  Render a detected GFM table as bold-heading + bullet groups.

    Uses the same alignment logic as Telegram's renderer: for non-row-label
    tables, ``data_cells = cells`` (the full row) and the bullet whose value
    duplicates the heading is skipped.  This keeps header→value alignment
    correct.
       rR   r   r   r"   N)startzRow c              3      K   | ]}||V  	d S r   r   )r   cells     r   	<genexpr>z&_render_table_block.<locals>.<genexpr>g  s'      ;;Td;D;;;;;;r   rQ   u   • z: z**rn   )r'   joinr   	enumeratenextextendzipappend)r   headersfirst_data_rowhas_row_label_colrendered_groupsindexrowcellsheading
data_cellsbulletsheadervaluegroup_liness                 r   _render_table_blockr   J  sd    ;!yy%%%&{1~66G
7||ayy%%% {a 	!Q000 
 N++s7||a/??!#OABBq999 7 7
s(-- 	"'HE!HHeAhh...GqrrJJ;;U;;;^E^^LLGJz??S\\))rdc'llS__&DEFFFF__s7||++#Nc'llN3J *55 	5 	5MFE$ ')9)9NN3&33E334444'G'''2'2tyy556666;;'''r   c                    d| vsd| vr| S |                      d          }g }d}d}|t          |          k     r||         }|                                }|                    d          r| }|                    |           |dz  }c|r|                    |           |dz  }d|v r|dz   t          |          k     rt
                              ||dz                      r|||dz            g}|dz   }|t          |          k     r]t          ||                   rH|                    ||                    |dz  }|t          |          k     rt          ||                   H|                    t          |                     |}e|                    |           |dz  }|t          |          k     d	                    |          S )	zuRewrite GFM pipe tables into bold-heading + bullet groups.

    Tables inside fenced code blocks are left alone.
    r   -rR   Fr   ```r"   r   )
splitr'   lstrip
startswithr   TABLE_SEPARATOR_REmatchr   r   r   )	rS   linesoutin_fenceir   r   r   js	            r   convert_table_to_bulletsr   {  s   
 $#T//JJtECH	A
c%jj..Qx;;==u%% 	#|HJJtFA 	JJtFA 4KKAE

"""((q1u66 #  q1u.KAAc%jj..\%(%;%;.""58,,,Q c%jj..\%(%;%;. JJ*;77888A

4	Q; c%jj..> 99S>>r   )platform_labeldisplay_labeldefaultslogger_
log_prefixr   r   r   zlist[str] | Noner   zlogging.Logger | Nonezlist[re.Pattern]c          	          |pt           }|%|p|}| }|g S t          |t                    r|g}t          |t                    s,|                    d||t          |          j                   g S g }	|D ]}
t          |
t                    r|
                                s,	 |	                    t          j
        |
t          j                             `# t          j        $ r#}|                    d|||
|           Y d}~d}~ww xY w|	r%|                    d|t          |	          |           |	S | t          |pg           }nt          | t                    rv|                                 }	 |rt          j        |          ng }n# t"          $ r d}Y nw xY wt          |t                    r|nd |                                D             }nt          | t                    r| }n| g}g }	|D ]}
t          |
                                          }|s&	 |	                    t          j
        |t          j                             Z# t          j        $ r"}|                    d|||           Y d}~d}~ww xY w|	S )aZ  Compile regex wake-word/mention patterns from config or env values.

    Two adapter families share this logic:

    * **Config-style** (dingtalk, telegram): pass ``platform_label`` (e.g.
      ``"dingtalk"``). ``raw`` is the value from ``config.extra`` after env
      fallback parsing; must be a list or string, anything else logs a warning
      and yields ``[]``. Non-string entries are skipped. A summary info log is
      emitted when patterns load.
    * **Wakeword-style** (photon, bluebubbles): pass ``defaults``. ``raw`` may
      be None (use defaults), a string (JSON list or comma/newline separated),
      a list, or a scalar (wrapped in a list). Entries are coerced via
      ``str()``.

    ``log_prefix`` is interpolated into every log message so per-adapter log
    output stays byte-identical to the historical inline implementations.
    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                 f    g | ].}|                     d           D ]}|                                /S ),)r   ry   )r   r   parts      r   r   z,compile_mention_patterns.<locals>.<listcomp>  sQ     <
 <
 <


3<
 <
  JJLL<
 <
 <
 <
r   z#[%s] Invalid mention pattern %r: %s)rb   r   r>   r   warningtyper8   ry   r   recompile
IGNORECASEerrorinfor'   r   r   ra   
splitlines)rawr   r   r   r   r   logdisplaypatternscompiledpatternexcrS   loadeds                 r   compile_mention_patternsr     s   4 
VC!1>Ih$$ 	" zH(D)) 	KKKX'	   I%' 	 	Ggs++ 7==?? 	
7BM B BCCCC8   <         	HH6H	    {B''	C		 yy{{	)-5TZ%%%2FF 	 	 	FFF	'55 
66 <
 <
))<
 <
 <

 
C		 5H V V7||!!## 		VOOBJtR];;<<<<x 	V 	V 	VKK=z4QTUUUUUUUU	VOsB   !2CD#DD0F	 	FF2I

I;I66I;c                 j    d}|                      d          D ]}|                    d          r| }|S )zReturn True when *text* ends inside an unclosed ``` code fence.

    Scans line by line, toggling in/out state on lines starting with ```.
    An odd number of toggles means the trailing fence is unclosed.
    FrR   r   )r   r   )rS   r   r   s      r   text_has_unclosed_fencer      sE     H

4   $ $??5!! 	$#|HOr   c                     |                                  }|sdS |                    d          d                                         }|                    d          o|                    d          S )z=True when the last non-empty line starts and ends with ``|``.FrR   r   )rstripr   ry   r   endswith)rS   trimmed	last_lines      r   text_ends_with_table_rowr  -  se    kkmmG ud##B'--//I$$@););C)@)@@r   c                 P    |                                                      d          S )z<True when an atomic block is a code block (starts with ```).r   )r   r   rz   s    r   is_fence_atomr  6  s    ;;==##E***r   c                     |                      d          d                                         }|                    d          o|                    d          S )z?True when an atomic block is a table (first line is ``|...|``).rR   r   r   )r   ry   r   r   )rS   
first_lines     r   is_table_atomr  ;  sK    D!!!$**,,J  %%B**=*=c*B*BBr   u   [。！？.!?]\nc                    |pt           } ||           |k    r| dfS |t           u r| d|         }nQdt          |           }}||k     r0||z   dz   dz  } || d|                   |k    r|}n|dz
  }||k     0| d|         }|                    d          }|dk    r| d|dz            | |dz   d         fS d}	t                              |          D ]}
|
                                }	|	dk    r| d|	         | |	d         fS |                    d          }|dk    r| d|dz            | |dz   d         fS t          |          }| d|         | |d         fS )	a  Find the nearest paragraph boundary within *max_chars*; return (head, tail).

    Split priority:
      1. Blank line (paragraph boundary)
      2. Newline after sentence-ending punctuation (CJK and ASCII)
      3. Last newline
      4. Force split at the *max_chars* window boundary

    ``head + tail == text`` always holds.  *len_fn* allows measuring in
    custom units (e.g. UTF-16 code units); a binary search finds the largest
    prefix that fits when it is provided.
    rQ   Nr   r"   r   rn   r   rR   )r'   rfind_SENTENCE_END_NEWLINE_REfinditerend)rS   	max_charslen_fn_lenwindowlohimidposbest_posmcuts               r   split_at_paragraph_boundaryr  D  s    =SDtDzzYRxs{{jyj!CIIB2gg7Q;1$CtD#J9,,1W 2gg crc ,,v

C
QwwHS1WH~tC!GHH~-- H%..v66  5577!||IXIXYY// ,,t

C
QwwHS1WH~tC!GHH~-- f++C:tCDDz!!r   	list[str]c                    |                      d          }g g d}dt          dt          fd}dfd}|D ]}|rJ                    |           |                    d          rt                    d	k    rd} |             O|                    d          r" |             d
}                    |            ||          r3r |d                   s
 |                                 |           |                                dk    r |             r |d                   r
 |                                 |            |             S )a  Split markdown into indivisible "atomic blocks".

    Atoms are: fenced code blocks (``` ... ``` inclusive), tables
    (consecutive ``|...|`` lines), and plain paragraphs separated by blank
    lines.  Blank lines are separators and belong to no atom.
    rR   Fr   r   c                 ~    |                                  }|                    d          o|                    d          S )Nr   )ry   r   r   r   s     r   _is_table_linez,split_markdown_atoms.<locals>._is_table_line  s6    ::<<""3''BH,=,=c,B,BBr   Nc                      rTd                               } |                                 r                    |                                             d S d S )NrR   )r   ry   r   r6   )atomatomscurrent_liness    r   _flush_currentz,split_markdown_atoms.<locals>._flush_current  s`     	"99]++Dzz|| #T"""!!!!!		" 	"r   r   r"   Tr   rQ   rl   )r   r>   r?   r   r   r'   ry   )rS   r   r   r  r"  r   r   r!  s         @@r   split_markdown_atomsr#  w  s    JJtEE!#MHCS CT C C C C" " " " " " "  ' ' 	'  &&&u%% !#m*<*<q*@*@    __U## 	'NH  &&&&^D!! 		' !^^M"4E%F%F !     &&&&ZZ\\RN !b0A!B!B !     &&&&NLr   
prev_chunk
next_chunkc                    |                                  }|                                }|                    d          s|                    d          rdS t	          |           r]|r-|                    d          d                                         nd}|                    d          r|                    d          rdS dS )zInfer the separator (``'\n'`` or ``'\n\n'``) between two chunks.

    Single newline when the boundary sits at a code fence or a continued
    table; paragraph separator otherwise.
    r   rR   r   rQ   r   rn   )r   r   r   r   r  r   ry   )r$  r%  prev_trimmednext_trimmedr  s        r   infer_block_separatorr)    s     $$&&L$$&&LU## |'>'>u'E'E t
++ <HP\''--a066888b
  %% 	**=*=c*B*B 	46r   chunksc                    | sg S g }d}|t          |           k     r| |         }t          |          rj|dz   t          |           k     rTt          || |dz                      }||z   | |dz            z   }|dz  }t          |          r|dz   t          |           k     T|                    |           |dz  }|t          |           k     |S )zStream-aware fence merge: rejoin chunks truncated mid-fence.

    While chunk *i* has an unclosed fence and a successor exists, merge the
    successor into it using :func:`infer_block_separator`.
    r   r"   )r'   r   r)  r   )r*  resultr   currentseps        r   merge_streaming_fencesr/    s      	F	A
c&kk//)%g.. 	1q53v;;3F3F'A??CmfQUm3GFA &g.. 	1q53v;;3F3F 	g	Q c&kk// Mr   c                    t          |           dk    r| S g }d}| D ]}|d| dnd}|du}|pd}|                    d          D ]n}|                                }|                    d          rC|rd}d}2d}|dd                                         }	|	r|	                                d	         nd}o||z   }
|r|
d
z  }
|}nd}|                    |
           |S )aC  Close orphaned ``` fences at each chunk boundary and reopen on the next.

    When a split lands inside a triple-backtick code block, close the fence
    at the end of the head chunk and reopen it (with the original language
    tag) at the start of the next, so every delivered chunk is
    fence-balanced on its own.
    r"   Nr   rR   rQ   FTr   r   z
```)r'   r   ry   r   r   )r*  r   
carry_langchunkprefixin_codelangr   r   tagbodys              r   balance_fences_across_chunksr8    s-    6{{aCJ  )3)?%z%%%%RD(RKK%% 		9 		9Dzz||H""5)) 9 9#GDD"G"122,,,..C-08399;;q>>bD~ 	GODJJJ

4Jr   rn   c                 V   |pt           }g }d}| D ]}|s|n| | | }	 ||	          |k    r|	} |r|                    |           d} ||          |k    r|}K||                     ||                     l|                    |           |r|                    |           |S )a  Greedily pack pre-split *blocks* into chunks of at most *max_length*.

    Blocks are joined with *sep* while they fit.  A block that alone exceeds
    the limit is passed to *overflow(block)* (which must return a list of
    chunks) when provided, else emitted as-is.
    rQ   )r'   r   r   )
blocks
max_lengthr  r.  overflowr  packedr-  block	candidates
             r   greedy_pack_blocksr@    s     =SDFG ! !!(FEE.F#.Fu.F.F	4	??j((G 	MM'"""G4;;*$$GMM((5//****MM%     gMr   TF)prefer_paragraphsbalance_fencesc                    |pt           }| sg S |rt          | ||          }nt          | ||          }|rt          |          }|S )u  Split markdown text into chunks of at most *limit*, respecting fences.

    Two strategies, selected by ``prefer_paragraphs``:

    ``prefer_paragraphs=True`` (yuanbao-derived, the richest):
      Extract atomic blocks (code fences, tables, paragraphs), greedily merge
      them up to *limit*, split still-oversized non-atomic chunks at
      paragraph boundaries, then re-merge small neighbours.  Code blocks and
      tables are never split in the middle; a single atom larger than
      *limit* is emitted oversize rather than broken.

    ``prefer_paragraphs=False`` (stream_consumer-derived):
      Newline-preferred hard splitting with headroom reserved for fence
      markers when the text contains ```.

    ``balance_fences=True`` post-processes the chunks so a split inside a
    code block closes the fence on the head chunk and reopens it (with the
    language tag) on the tail — required by callers whose chunks are
    delivered as independent messages that each must render standalone.
    )r'   _chunk_markdown_paragraphs_chunk_newline_preferredr8  )rS   limitr  rA  rB  r  r*  s          r   split_text_fence_awarerG    s`    8 =SD 	 =+D%@@)$t<< 6-f55Mr   c                    |pt           } ||           |k    r| gS t          |           }g t                      }g d}dfd}|D ]} ||          }	rdnd}
||
z   |	z   }||k    rr |             g d}d}
s\|	|k    rVt          |          st	          |          r8|                    t                                                   |                               |           ||
|	z   z  } |             g }t                    D ]\  }} ||          |k    r|                    |           *||v r|                    |           Dt          |          r|                    |           i|} ||          |k    rQt          |||          \  }}|s|d|         ||d         }}|r|                    |            ||          |k    Q|r|                    |           t          |          dk    rR|d         g}|dd         D ]<}|d         }|d	z   |z   } ||          |k    r||d<   '|                    |           =|}d
 |D             S )zCYuanbao-derived paragraph/atom chunking pipeline (see module docs).r   r   Nc                  `    r*                      d                                         d S d S )Nrn   )r   r   )r*  current_partss   r   _flush_partsz0_chunk_markdown_paragraphs.<locals>._flush_partsQ  s9     	6MM&++m4455555	6 	6r   r   )r  r"   r   rn   c                     g | ]}||S r   r   )r   cs     r   r   z._chunk_markdown_paragraphs.<locals>.<listcomp>  s    ###!#A###r   rl   )
r'   r#  setr  r  addr   r   r   r  )rS   r  r  r  r   indivisible_setcurrent_lenrK  r  atom_lensep_lenprojected_lenr,  idxr2  	remainingheadmergedprevcombinedr*  rJ  s                       @@r   rD  rD  A  s8   =SDtDzzYv !&&E F"%%%O!#MK6 6 6 6 6 6 6  * *4::$+!!!#g-89$$$LNNNMKG 	y(("4(( ),9$,?,? )F,,,MM$T"""w))LNNN F'' % %
U4;;)##MM%   /!!MM%   "5)) 	MM%   	d9oo	))99V  OD)  O"+JYJ"79::9Ni $d### d9oo	))  	%MM)$$$ 6{{Q%aykABBZ 	% 	%E":Df}u,HtH~~**%r

e$$$$##v####r   c                     ||           |k    r| gS |}d| v rt          |dz
  |dz  d          }ddlm} g }| } ||          |k    rx ||||          }|                    dd|          }||dz  k     r|}|                    |d|                    ||d                             d          } ||          |k    x|r|                    |           |S )	zCStream-consumer-derived newline-preferred splitting (no balancing).r      r   r"   r   )_custom_unit_to_cprR   N)maxgateway.platforms.baser]  r
  r   r   )	rS   rF  r  split_limitr]  r*  rV  
_cp_budgetsplit_ats	            r   rE  rE    s$   vd||uv K}}%"*eqj!44 :99999FI
&

k
)
)''	;GG
??4J77jAo%%!Hi		*+++hii(//55	 &

k
)
)  !i   Mr   r   )r*  r  r   r  )Nrn   N):r;   rW   r   loggingr   r&   pathlibr   typingr   r   utilsr   r_  r   	getLoggerr8   rb   r	   rA   r   DOTALLro   rq   rr   rs   rt   ru   	MULTILINErv   rw   rx   r>   r{   r}   r   r   r?   r   r   r   r   r   r   r   r  r  r  r  r  r#  r)  r/  r8  r@  rG  rD  rE  r   r   r   <module>rj     s       				        & & & & & & & & # # # # # # 4333333		8	$	$@ @ @ @ @ @ @ @LR R R R R R R Rp 2:&	22"*\2955?KK2:?KK 455"*Z((bj552:/00BJy))      *= = = = = = = =F
+ 
+ 
+ 
+ 
+ 
+,  RZ7  
.s .t . . . .
!3 
!49 
! 
! 
! 
!.(T#Y .(3 .( .( .( .(b+3 +3 + + + +j "& $#''+` ` ` ` $J	`
 :` !` %` ` ` ` `h
# 
$ 
 
 
 
A3 A4 A A A A+ + + + + +
C C C C C C &2:&9:: 0" 0" 0" 0"f/s /{ / / / /dc s s    (   .! ! ! !H   B (
 ( ( ( ( (VR$ R$ R$ R$j    r   