Ë
    áß²j�3  ã                   ó¤   — d Z ddlZddlZddlmZmZ ddlmZmZm	Z	m
Z
 ddlmZ dZdZdZd	Zd
edefd„Zdedededededz  f
d„Z G d„ de«      Zy)a	  Abstract base class for pluggable context engines.

A context engine decides when/how conversation context is compacted near the token
limit, tracks usage, and may expose tools. ContextCompressor is the default;
``context.engine`` selects a plugin (``plugins/context_engine/<name>/``); one is active.
Lifecycle: on_session_start() -> per API response update_from_response() -> per turn
should_compress() / compress() -> on_session_end() at real session boundaries only
(CLI exit, /reset, gateway expiry), never per-turn.
é    N)ÚABCÚabstractmethod)ÚAnyÚDictÚListÚOptional)Úredact_sensitive_textip  i   iÜ  z+
...[memory provider context truncated]...
Úmemory_contextÚreturnc                 ó˜   — t        | j                  «       dd¬«      }t        |«      t        k  r|S |dt         t
        z   |t         d z   S )zBPrepare provider context for a context-engine/LLM egress boundary.T)ÚforceÚredact_url_credentialsN)r	   ÚstripÚlenÚMEMORY_CONTEXT_MAX_CHARSÚ_MEMORY_CONTEXT_HEAD_CHARSÚ!_MEMORY_CONTEXT_TRUNCATION_MARKERÚ_MEMORY_CONTEXT_TAIL_CHARS)r
   Ú	sanitizeds     ú9/home/ubuntu/.hermes/hermes-agent/agent/context_engine.pyÚsanitize_memory_contextr      sT   € ä% n×&:Ñ&:Ó&<ÀDÐaeÔf€IÜ
ˆ9ƒ~Ô1Ò1ØÐØÐ0Ô0Ð1Ô4UÑUÐXaÔc}Ðb}Ðb~ÐXÑÐó    ÚengineÚphaseÚdefault_messageÚcontextc                ó¨   — t        | dd«      syt        | dd«      }t        |«      r |d||dœ|¤Žn|}|€yt        |«      j                  «       xs dS )zâHost-visible status for an automatic compaction event; ``None`` = emit nothing.

    Engines suppress via ``emit_automatic_compaction_status = False`` or
    customize via ``get_automatic_compaction_status_message(...)``.
    Ú emit_automatic_compaction_statusTNÚ'get_automatic_compaction_status_message)r   r   © )ÚgetattrÚcallableÚstrr   )r   r   r   r   Ú	formatterÚmessages         r   Ú#automatic_compaction_status_messager&   !   sb   € ô �6Ð=¸tÔDØÜ˜Ð IÈ4ÓP€IÜT\Ð]fÔTg‰iÐP˜e°_ÑPÈÒPÐm|€GØ€ØÜˆw‹<×ÑÓÒ' 4Ð'r   c                   ót  — e Zd ZU dZeedefd„«       «       ZdZe	e
d<   dZe	e
d<   dZe	e
d<   dZe	e
d<   dZe	e
d	<   dZe	e
d
<   dZee
d<   dZe	e
d<   dZe	e
d<   dZee
d<   edeeef   ddfd„«       ZedAde	defd„«       ZdAde	ddfd„Ze	 	 dBdeeeef      dee	   dee   dededeeeef      fd„«       Z	 dAdeeeef      de	dz  deeeeef      e	f   fd „Zdddd!œd"eeeef      d#eeeef      d$eeef   d%e	deeeef      f
d&„Z dAdeeeef      deeef   d'eddfd(„Z!deeeef      defd)„Z"d*e	defd+„Z#d,ed-ed.ededz  fd/„Z$deeeef      defd0„Z%d1eddfd2„Z&d1edeeeef      ddfd3„Z'dCd4„Z(deeeef      fd5„Z)d6ed7eeef   defd8„Z*deeef   fd9„Z+dDd:„Z,	 	 dEd;ed	e	d<ed=ed>ed?eddfd@„Z-y)FÚContextEnginez.Base class all context engines must implement.r   c                  ó   — y)z,Short identifier (e.g. 'compressor', 'lcm').Nr    ©Úselfs    r   ÚnamezContextEngine.name3   ó   � r   r   Úlast_prompt_tokensÚlast_completion_tokensÚlast_total_tokensÚthreshold_tokensÚcontext_lengthÚcompression_countg      è?Úthreshold_percenté   Úprotect_first_né   Úprotect_last_nTr   ÚusageNc                  ó   — y)a=  Update tracked token usage after every LLM call.

        ``prompt_tokens``/``completion_tokens``/``total_tokens`` are always present; the
        canonical buckets (``input_tokens``, ``output_tokens``, ``cache_read_tokens``,
        ``cache_write_tokens``, ``reasoning_tokens``) are optional on older hosts.
        Nr    )r+   r9   s     r   Úupdate_from_responsez"ContextEngine.update_from_responseK   r-   r   Úprompt_tokensc                  ó   — y)z0Return True if compaction should fire this turn.Nr    ©r+   r<   s     r   Úshould_compresszContextEngine.should_compressT   r-   r   ztuple[bool, str | None]c                 ó(   — | j                  |«      dfS )a  Return ``(should_compress, reason)``.

        Engines with block reasons (summary-LLM cooldown, anti-thrashing guard) override
        this so callers can warn instead of silently skipping; the default keeps plugin
        engines from raising AttributeError.
        N)r?   r>   s     r   Úshould_compress_infoz"ContextEngine.should_compress_infoX   s   € ð ×#Ñ# MÓ2°DÐ8Ð8r   ÚmessagesÚcurrent_tokensÚfocus_topicr   r
   c                  ó   — y)u‡  Compact ``messages`` into a valid OpenAI-format list that fits the budget.

        ``focus_topic`` comes from manual ``/compress <focus>`` (prioritise that topic);
        ``force`` asks to bypass an engine-owned cooldown; ``memory_context`` is provider
        text for the handoff prompt. Older engines may omit optional parameters â€” the
        host filters them by signature.
        Nr    )r+   rB   rC   rD   r   r
   s         r   ÚcompresszContextEngine.compressa   r-   r   c                 ó
   — |dfS )aM  Deterministically trim old tool-result payloads without an LLM call.

        Runs on a low, cost-oriented trigger independent of ``should_compress`` so
        large-window engines reclaim re-sent tool output long before full compaction.
        Returns ``(messages, n_pruned)``; the default no-op keeps older engines safe.
        r   r    )r+   rB   rC   s      r   Úprune_tool_results_onlyz%ContextEngine.prune_tool_results_onlyn   s   € ð ˜ˆ{Ðr   )Úconversation_messagesÚincoming_messageÚbudget_tokensÚrequest_messagesrI   rJ   rK   c                 ó   — y)u…  Optionally *select* (replace) the context for THIS request, pre-generation.

        Runs on every provider request (also retries), independent of
        ``should_compress()``: ``compress()`` shrinks over-long context, this swaps in a
        different one (retrieval, topic routing, branch switching). Return ``None`` to
        leave the request unchanged. The returned list is request-only â€” it MUST NOT be
        treated as persisted transcript state (session DB history is untouched); unlike
        ``pre_llm_call`` it may replace the list. The host runs it before prompt
        cache-control and every request sanitizer, so a malformed replacement never
        reaches the provider and the default no-op keeps the request byte-identical;
        an engine that replaces the list changes its own cache prefix (breakpoints are
        re-derived on the selected list). ``request_messages`` is the assembled request
        (system prompt + history + ephemeral prefill); ``conversation_messages`` is the
        persisted history for reference only (do not mutate); ``budget_tokens`` is the
        model's context length or 0 if unknown.
        Nr    )r+   rL   rI   rJ   rK   s        r   Úselect_contextzContextEngine.select_contexty   s   € ð( r   Úkwargsc                  ó   — y)a¤  Observe a finished turn (complement of ``select_context()``) to index/update
        routing state for the next request.

        Best-effort, not guaranteed: fires from the normal finalization seam only; some
        abnormal early returns (content-policy block, provider terminal failure) skip it.
        ``messages`` is a read-only shallow copy (return value ignored; never rely on
        transcript mutation). ``usage`` has the ``update_from_response`` shape and is
        ``None`` when no provider response was reached (interrupt). ``kwargs`` may include
        ``turn_id``, ``task_id``, ``api_call_count``, ``interrupted``, ``failed``, ``turn_exit_reason``.
        Nr    )r+   rB   r9   rO   s       r   Úon_turn_completezContextEngine.on_turn_complete�   s   € ð r   c                  ó   — y)zOCheap rough check before the API call (no real token count yet); default skips.Fr    ©r+   rB   s     r   Úshould_compress_preflightz'ContextEngine.should_compress_preflightœ   s   € àr   Úrough_tokensc                  ó   — y)z—True when preflight should trust recent real usage over the noisy rough
        estimate (avoids re-compacting after a compressed request already fit).Fr    )r+   rU   s     r   Ú$should_defer_preflight_to_real_usagez2ContextEngine.should_defer_preflight_to_real_usage    s   € ð r   r   r   r   c                ó"   — | j                   r|S dS )aB  User-visible status for automatic compaction, or ``None`` to suppress it.

        ``phase`` is the host call site (``"preflight"`` / ``"compress"``); ``context``
        carries best-effort ``approx_tokens`` / ``threshold_tokens``. Warnings, errors
        and manual ``/compress`` are not governed by this hook.
        N)r   )r+   r   r   r   s       r   r   z5ContextEngine.get_automatic_compaction_status_message¥   s   € ð #'×"GÒ"GˆÐQÈTÐQr   c                  ó   — y)z”Preflight guard for gateway ``/compress``: False reports "nothing to
        compress yet" without an LLM call (e.g. transcript entirely protected).Tr    rS   s     r   Úhas_content_to_compressz%ContextEngine.has_content_to_compress°   s   € ð r   Ú
session_idc                  ó   — y)zVSession begins: load persisted state. kwargs may include hermes_home, platform, model.Nr    )r+   r[   rO   s      r   Úon_session_startzContextEngine.on_session_startµ   r-   r   c                  ó   — y)uL   Real session boundary (CLI exit, /reset, gateway expiry) â€” never per-turn.Nr    )r+   r[   rB   s      r   Úon_session_endzContextEngine.on_session_end¸   r-   r   c                 ó<   — d| _         d| _        d| _        d| _        y)zO/new or /reset: reset per-session state (default: counters and token tracking).r   N)r.   r/   r0   r3   r*   s    r   Úon_session_resetzContextEngine.on_session_reset»   s$   € ð #$ˆÔØ&'ˆÔ#Ø!"ˆÔØ!"ˆÕr   c                 ó   — g S )z>Tool schemas this engine exposes to the agent (default: none).r    r*   s    r   Úget_tool_schemaszContextEngine.get_tool_schemasÌ   s   € àˆ	r   r,   Úargsc                 ó6   — t        j                  dd|› �i«      S )z†Handle a call to one of this engine's tools; must return a JSON string.
        kwargs may include ``messages`` (live in-memory list).ÚerrorzUnknown context engine tool: )ÚjsonÚdumps)r+   r,   rd   rO   s       r   Úhandle_tool_callzContextEngine.handle_tool_callÐ   s    € ô �z‰z˜7Ð&CÀDÀ6Ð$JÐKÓLÐLr   c                 óÊ   — t        | j                  d«      }|| j                  | j                  | j                  rt	        d|| j                  z  dz  «      nd| j
                  dœS )z:Status dict with the standard fields run_agent.py expects.r   éd   )r.   r1   r2   Úusage_percentr3   )Úmaxr.   r1   r2   Úminr3   )r+   Úlast_prompts     r   Ú
get_statuszContextEngine.get_statusÕ   sc   € ô ˜$×1Ñ1°1Ó5ˆà"-Ø $× 5Ñ 5Ø"×1Ñ1ØRV×ReÒReœS  k°D×4GÑ4GÑ&GÈ#Ñ&MÔNÐklØ!%×!7Ñ!7ñ
ð 	
r   c                 ó,   — t        j                  | «      S )al  Per-agent instance of a plugin-registered engine (the plugin system holds ONE shared
        instance; every AIAgent gets its own so a child's update_model() cannot mutate the parent's).
        Override when the engine holds uncopyable state (locks, DB connections): return a fresh
        engine sharing the durable backend and copying only mutable budget state.)ÚcopyÚdeepcopyr*   s    r   Úclone_for_agentzContextEngine.clone_for_agentâ   s   € ô
 �}‰}˜TÓ"Ð"r   ÚmodelÚbase_urlÚapi_keyÚproviderÚapi_modec                 óþ   — || _         ddlm} t        | d«      s| j                  | _         ||t        | di «      | j
                  |«      | _        | j                  | _        t        || j                  z  «      | _	        y)u.  Model switch / fallback: recompute threshold_tokens (override for more).

        Per-model threshold override (longest substring match), else the raw config
        percent â€” snapshotted ONCE so repeated switches fall back to the configured
        value, not the previous model's override.
        r   )Úresolve_model_thresholdÚ_config_threshold_percentÚmodel_thresholdsN)
r2   Úagent.context_compressorr{   Úhasattrr4   r|   r!   Ú_base_threshold_percentÚintr1   )r+   ru   r2   rv   rw   rx   ry   r{   s           r   Úupdate_modelzContextEngine.update_modelé   sv   € ð -ˆÔÝDÜ�tÐ8Ô9Ø-1×-CÑ-CˆDÔ*Ù'>Ø”7˜4Ð!3°RÓ8¸$×:XÑ:XÐZbó(dˆÔ$à!%×!=Ñ!=ˆÔÜ # N°T×5KÑ5KÑ$KÓ LˆÕr   )N)NNFÚ )r   N)r   r(   )rƒ   rƒ   rƒ   rƒ   ).Ú__name__Ú
__module__Ú__qualname__Ú__doc__Úpropertyr   r#   r,   r.   r�   Ú__annotations__r/   r0   r1   r2   r3   r4   Úfloatr6   r8   r   Úboolr   r   r;   r?   rA   r   r   rF   ÚtuplerH   rN   rQ   rT   rW   r   rZ   r]   r_   ra   rc   ri   rp   rt   r‚   r    r   r   r(   r(   0   sÈ  … Ù8àØð;�cò ;ó ó ð;ð  Ð˜ÓØ"#Ð˜CÓ#ØÐ�sÓØÐ�cÓØ€N�CÓØÐ�sÓð  $Ð�uÓ#Ø€O�SÓØ€N�CÓð .2Ð$ dÓ1àð¨$¨s°C¨x©.ð ¸Tò ó ðð ñ?¨Sð ?¸Dò ?ó ð?ñ9°#ð 9ÐAZó 9ð àNRØVXñ
Ø˜T # s (™^Ñ,ð
Ø>FÀs¹mð
à˜c‘]ð
à26ð
àPSð
ð 
ˆd�3˜�8‰nÑ	ò
ó ð
ð LPñ	Ø˜T # s (™^Ñ,ð	Ø>AÀD¹jð	à	ˆt�D˜˜c˜‘NÑ# SÐ(Ñ	)ó	ð hlØ+/ÀaòØ $ T¨#¨s¨(¡^Ñ 4ðØPTÐUYÐZ]Ð_bÐZbÑUcÑPdðà˜s C˜x™.ðà@Cðð 
ˆd�3˜�8‰nÑ	óñ,¨¨d°3¸°8©nÑ)=ð ÀdÈ3ÐPSÈ8Ánð Ðgjð Ðosó ð°$°t¸CÀ¸H±~Ñ2Fð È4ó ðÀð Èó ð
	RØð	RØ.1ð	RØ>Að	Rà	ˆt‰ó	Rð°°T¸#¸s¸(±^Ñ0Dð Èó ð
e¨3ð e¸Tó eð[¨ð [¸¸TÀ#ÀsÀ(¹^Ñ8Lð [ÐQUó [ó#ð" $ t¨C°¨H¡~Ñ"6ó ðM Sð M°°S¸#°X±ð MÈSó Mð

˜D  c ™Nó 
ó#ð SUØ,.ñMØðMØ*-ðMØ9<ðMØLOðMàðMà&)ðMð 
ôMr   r(   )r‡   rr   rg   Úabcr   r   Útypingr   r   r   r   Úagent.redactr	   r   r   r   r   r#   r   r&   r(   r    r   r   ú<module>r�      s�   ðñó Û ß #ß ,Ó ,å .ð !Ð Ø"Ð Ø"Ð Ø$SÐ !ð@¨Cð @°Có @ð(°ð (¸sð (ÐUXð (Ðehð (ÐmpÐswÑmwó (ôJM�Cõ JMr   