+
    „WÝj$  ã                  ó‚   € R t ^ RIHt ^ RIHtHtHtHtHt ^t	^dt
R R ltR R ltR R ltR	 R
 ltR R ltR R ltR# )u  Boundary-aware partial compression â€” "summarize up to here".

* **Role alternation.** The compressed head ends with summary/handoff content (assistant- or user-
role, possibly a trailing todo snapshot). The verbatim tail must begin with a ``user`` message so
the rejoined history keeps the userâ†”assistant alternation that providers validate.
* **No silent context mutation.** Manual, user-invoked; rotates the session exactly like
``/compress`` (via the caller), so the prompt-cache reset is explicit and expected.
)Úannotations)ÚAnyÚDictÚListÚOptionalÚTuplec               ó    € V ^8„  d   QhRRRR/# )é   Úraw_argsÚstrÚreturnztuple[bool, int, Optional[str]]© )Úformats   "ÚOC:\Users\johnn\AppData\Local\hermes\hermes-agent\hermes_cli\partial_compress.pyÚ__annotate__r      s   € ÷ 2ñ 2¨#ð 2Ð2Qñ 2ó    c                ó‚  € T ;'       g    RP                  4       pV'       g
   R\        R3# VP                  4       pVP                  R4      '       d   V\	        R4      R pV\	        R4      R pVP                  4       pV'       d
   V^ ,          MRpVR8X  d.   \	        V4      ^8¼  d   \        V^,          4      M\        pRVR3# VR
9   d&   \	        V4      ^8¼  d   R\        V^,          4      R3# VP                  R4      '       d&   R\        VP                  R	^4      ^,          4      R3# R\        T;'       g    R3# )a  Parse the argument string after ``/compress`` into ``(partial, keep_last, focus_topic)``.

``here [N]`` / ``up to here [N]`` / ``--keep N`` / ``-k N`` / ``--keep=N`` select the
boundary-aware form; anything else is a focus topic for full ``/compress <focus>``.
Ú FNz
up to herezup to ÚhereTz--keep=Ú=)z--keepz-k)ÚstripÚDEFAULT_KEEP_LASTÚlowerÚ
startswithÚlenÚsplitÚ_coerce_keep)r
   ÚtextÚloweredÚtokensÚheadÚkeeps   &     r   Úparse_partial_compress_argsr"      s  € ð �NˆN˜×!Ñ!Ó#€DßØÔ'¨Ð-Ð-à�j‰j‹l€GØ×Ñ˜,×'Ò'Øœ#˜h›-˜/Ð*ˆØ”C˜“M�OÐ$ˆà�]‰]‹_€Fßˆ6�!Ž9 B€Dàˆv„~Ü*-¨f«+¸Ô*:Œ|˜F 1�IÔ&Ô@QˆØ�T˜4ÐÐØÐÔ¤C¨£K°1Ô$4Ø”\ &¨¥)Ó,¨dÐ2Ð2Ø‡��y×!Ò!Ø”\ $§*¡*¨S°!Ó"4°QÕ"7Ó8¸$Ð>Ð>àÔ# T§\ \¨TÐ1Ð1r   c               ó    € V ^8„  d   QhRRRR/# )r	   r
   r   r   ztuple[str, bool, bool]r   )r   s   "r   r   r   2   s   € ÷ /ñ / Sð /Ð-Cñ /r   c                óä   € RpRp. pT ;'       g    RP                  4        F:  pVP                  4       pVR9   d   RpK  VR8X  d   RpK)  VP                  V4       K<  	  RP                  V4      W3# )aj  Strip ``--preview``/``--dry-run``/``--aggressive`` (anywhere in the string) and return
``(remaining_args, preview, aggressive_requested)``.

``preview`` means report what WOULD be compressed and change nothing. No surface implements an
LLM-free hard-truncate path, so callers surface "not supported" for ``--aggressive`` instead of
treating it as a focus topic.
Fr   Tz--aggressiveÚ )z	--previewz	--dry-runz--dryrun)r   r   ÚappendÚjoin)r
   ÚpreviewÚ
aggressiveÚkeptÚtokÚlows   &     r   Úextract_compress_flagsr-   2   ss   € ð €GØ€JØ€DØ—�˜B×%Ñ%Ö'ˆØ�i‰i‹kˆØÐ8Ô8ØŠGØ�NÔ"ØŠJà�K‰K˜Öñ (ð �8‰8�D‹>˜7Ð.Ð.r   c               ó0   € V ^8„  d   QhRRRRRRRRR	RR
R/# )r	   Úhistoryúlist[dict[str, Any]]ÚpartialÚboolÚ	keep_lastÚintÚfocus_topiczOptional[str]Úapprox_tokensr   zdict[str, Any]r   )r   s   "r   r   r   H   sD   € ÷ *ñ *Ø!ð*àð*ð ð*ð ð	*ð
 ð*ð ñ*r   c           
     óÂ  € \        V 4      p\        V 4      . rvRpV'       d   \        W4      w  rg\        V4      pRR\        V4       RV RVR R2.p	V'       d#   V	P	                  RV R	\        V4       R
24       MV'       d   V	P	                  R4       V'       d   V	P	                  RV R24       V	P	                  R4       R\        V4      R\        V4      RVRVRV	/# )u   Build the ``/compress --preview`` report â€” pure, no side effects.

Shared by the CLI and the gateway slash handler so both report the numbers the real run would
use. Returns ``head_count``/``tail_count``/``total``/``partial``/``lines`` (ready to print).
Fu   Preview â€” no changes made.zWould compress z of z message(s) (~Ú,z tokens currently in context).zBoundary: keeping the last z exchange(s) (z message(s)) verbatim.uR   Boundary: 'here' split would keep everything â€” falling back to full compression.zFocus topic: "Ú"z1Run the command again without --preview to apply.Ú
head_countÚ
tail_countÚtotalr1   Úlines)r   ÚlistÚ"split_history_for_partial_compressr2   r&   )
r/   r1   r3   r5   r6   r<   r    ÚtailÚeffective_partialr=   s
   &&&&&     r   Úsummarize_compress_previewrB   H   sü   € ô �‹L€EÜ�g“ ˆ$ØÐßÜ7¸ÓK‰
ˆä  ›JÐð 	'Ø
œ#˜d›)˜ D¨¨ð 0Ø˜1ÐÐ;ð	=ð€E÷
 Ø�‰Ø)¨)¨ð 5Ü�D“	ˆ{Ð0ð2õ	
÷ 
Ø�‰ÐiÔjßØ�‰�~ k ]°!Ð4Ô5Ø	‡L�LÐDÔEð 	”c˜$“iØ”c˜$“iØ�ØÐ$Ø�ðð r   c               ó    € V ^8„  d   QhRRRR/# )r	   Úvaluer   r   r4   r   )r   s   "r   r   r   u   s   € ÷ )ñ )˜ð ) ñ )r   c                óˆ   €  \        V 4      p\	        ^\        T\        4      4      #   \        \        3 d
    \        u # i ; i)z9Parse a keep-count token, clamping to [1, MAX_KEEP_LAST].)r4   Ú	TypeErrorÚ
ValueErrorr   ÚmaxÚminÚMAX_KEEP_LAST)rD   Úns   & r   r   r   u   sB   € ð!Ü�‹Jˆô ˆq”#�aœÓ'Ó(Ð(øô ”zÐ"ô !Ü Ò ð!ús   ‚' §AÁ Ac               ó$   € V ^8„  d   QhRRRRRR/# )r	   r/   r0   r3   r4   r   z1tuple[list[dict[str, Any]], list[dict[str, Any]]]r   )r   s   "r   r   r   ~   s&   € ÷ 2ñ 2Ø!ð2àð2ð 7ñ2r   c                ó   € \        V^4      pV '       g   . . 3# Rp^ p\        \        V 4      ^,
          RR4       F4  pW,          P                  R4      R8X  g   K!  TpV^,          pW18¼  g   K4   M	  V'       g   \	        V 4      . 3# V RV WR 3# )a»  Split ``history`` into ``(head, tail)``: head is summarized, tail is the last ``keep_last``
exchanges kept verbatim.

Exchanges are counted by ``user`` messages so the tail always starts on a user turn and
``compressed_head + tail`` keeps alternation valid. Returns ``(history, [])`` when the head
would be empty (or there are no user turns), signaling the caller to fall back to full
compression rather than rotating the session for a no-op.
NÚroleÚuseréÿÿÿÿ)rH   Úranger   Úgetr>   )r/   r3   ÚboundaryÚseenÚidxs   &&   r   r?   r?   ~   s›   € ô �I˜qÓ!€IßØ�2ˆvˆð €HØ€DÜ”S˜“\ AÕ% r¨2Ö.ˆØ�<×Ñ˜FÓ# vÖ-ØˆHØ�A�IˆDØÖ Ùñ /÷ Ü�G‹}˜bÐ Ð Ø�9�HÐ˜w yÐ1Ð1Ð1r   c               ó$   € V ^8„  d   QhRRRRRR/# )r	   Úcompressed_headr0   r@   r   r   )r   s   "r   r   r   �   s&   € ÷ ñ Ø)ðà
ðð ñr   c                ó"  € V'       g   \        V 4      # V '       g   \        V4      # \        V 4      p\        V4      pVR,          V^ ,          rTVP                  R4      VP                  R4      rvWg8X  d•   VR	9   dŽ   VP                  R4      pVP                  R4      p	\        V\        4      '       d8   \        V	\        4      '       d"   / VCRV RV	 2/CVR&   VR,          pW#,           # TP	                  RVR8X  d   RMRRR/4       W#,           # )
u)  Concatenate a compressed head with the verbatim tail, defending the seam's role alternation.

The head compressor's output shape isn't contractually guaranteed (a plugin engine could end on
a user turn). If the last head message and first tail message share a user/assistant role, the
tail's first string content is folded onto the head's last message; non-string (multimodal)
content gets a minimal bridging turn instead so nothing is lost. ``tool`` messages are left
alone â€” consecutive tool entries are the one legal repetition (parallel results).
rN   rO   Ú	assistantÚcontentz

:é   NNr   rP   )rO   rY   )r>   rR   Ú
isinstancer   r&   )
rW   r@   r    ÚrestÚlastÚfirstÚ	last_roleÚ
first_roleÚlast_contentÚfirst_contents
   &&        r   Úrejoin_compressed_head_and_tailrd   �   sú   € ÷ Ü�OÓ$Ð$ßÜ�D‹zÐä�Ó €DÜ�‹:€DØ�r•(˜D �Gˆ%Ø ŸH™H VÓ,¨e¯i©i¸Ó.?ˆzàÔ 9Ð0EÔ#EØ—x‘x 	Ó*ˆØŸ	™	 )Ó,ˆÜ�l¤C×(Ò(¬Z¸Äs×-KÒ-KØP˜$ÐP 	¨l¨^¸4À¸Ð+OÑPˆD�‰HØ˜•8ˆDð �;Ðð �K‰K˜°
¸fÔ0D¡È&ÐR[Ð]_Ð`Ôaà�;Ðr   N)Ú__doc__Ú
__future__r   Útypingr   r   r   r   r   r   rJ   r"   r-   rB   r   r?   rd   r   r   r   Ú<module>rh      sC   ðñõ #ç 3Õ 3ð Ð ð €õ2õ:/õ,*õZ)õ2÷>r   