§
    øžyjz$  ã                   ó�  — 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mZm	Z	m
Z
  ej        e¦  «        ZdZdZdZdZdZ ej        d	¦  «        Zd
Zdefd„Zdedefd„Zefdededeeef         fd„Zdedefd„Zdededefd„Zdededededef
d„Zde
dfdededede	dee z  dz  defd„Z!de
fde"e#         de	de"e#         fd„Z$dS ) aÉ  Tool result persistence -- preserves large outputs instead of truncating.

Defense against context-window overflow operates at three levels:

1. **Per-tool output cap** (inside each tool): Tools like search_files
   pre-truncate their own output before returning. This is the first line
   of defense and the only one the tool author controls.

2. **Per-result persistence** (maybe_persist_tool_result): After a tool
   returns, if its output exceeds the tool's registered threshold
   (registry.get_max_result_size), the full output is written INTO THE
   SANDBOX temp dir (for example /tmp/hermes-results/{tool_use_id}.txt on
   standard Linux, or $TMPDIR/hermes-results/{tool_use_id}.txt on Termux)
   via env.execute(). The in-context content is replaced with a preview +
   file path reference. The model can read_file to access the full output
   on any backend.

3. **Per-turn aggregate budget** (enforce_turn_budget): After all tool
   results in a single assistant turn are collected, if the total exceeds
   MAX_TURN_BUDGET_CHARS (200K), the largest non-persisted results are
   spilled to disk until the aggregate is under budget. This catches cases
   where many medium-sized results combine to overflow context.
é    N)ÚDEFAULT_PREVIEW_SIZE_CHARSÚBudgetConfigÚDEFAULT_BUDGETz<persisted-output>z</persisted-output>z/tmp/hermes-resultsÚHERMES_PERSIST_EOFÚ__budget_enforcement__z[^A-Za-z0-9_.-]+éx   Úreturnc                 ó  — | �|t          | dd¦  «        }t          |¦  «        r\	  |¦   «         }|r|                     d¦  «        pd}|› d�S n2# t          $ r%}t                               d|¦  «         Y d}~nd}~ww xY wt          S )z=Return the best temp-backed storage dir for this environment.NÚget_temp_dirú/z/hermes-resultsz"Could not resolve env temp dir: %s)ÚgetattrÚcallableÚrstripÚ	ExceptionÚloggerÚdebugÚSTORAGE_DIR)Úenvr   Útemp_dirÚexcs       ú?/home/ragecks/.hermes/hermes-agent/tools/tool_result_storage.pyÚ_resolve_storage_dirr   0   s½   € à
€Ý˜s N°DÑ9Ô9ˆÝ�LÑ!Ô!ð 	8ð8Ø'˜<™>œ>�ð ð 8Ø'Ÿš¨sÑ3Ô3Ð:°s�HØ&Ð7Ð7Ð7Ð7ð8øõ ð Hð Hð HÝ—’ÐAÀ3ÑGÔGÐGÐGÐGÐGÐGÐGøøøøðHøøøõ Ðs   ¤
A Á
A<ÁA7Á7A<Útool_use_idc                 ó®  — t          | pd¦  «        }t                               d|¦  «                             d¦  «        }||k    }|sd}d}|st	          |¦  «        t
          k    rlt          j        |                     d¦  «        ¦  «         	                    ¦   «         dd…         }|dt
          …          
                    d¦  «        pd}|› d|› �}|› d�S )	z3Return a single safe filename for a tool result id.Útool_resultÚ_z._-Tzutf-8Né   z.txt)ÚstrÚ_UNSAFE_RESULT_FILENAME_CHARSÚsubÚstripÚlenÚ_MAX_RESULT_FILENAME_STEMÚhashlibÚsha256ÚencodeÚ	hexdigestr   )r   Úraw_idÚ	safe_stemÚchangedÚdigests        r   Ú_safe_result_filenamer,   @   sâ   € å�Ð- Ñ.Ô.€FÝ-×1Ò1°#°vÑ>Ô>×DÒDÀUÑKÔK€IØ˜6Ò!€Gàð Ø!ˆ	Øˆàð ,•#�i‘.”.Õ#<Ò<Ð<Ý” §¢¨gÑ 6Ô 6Ñ7Ô7×AÒAÑCÔCÀCÀRÀCÔHˆØÐ8Õ8Ð8Ô9×@Ò@ÀÑGÔGÐXÈ=ˆ	Ø Ð+Ð+ 6Ð+Ð+ˆ	àÐÐÐÐó    ÚcontentÚ	max_charsc                 ó¢   — t          | ¦  «        |k    r| dfS | d|…         }|                     d¦  «        }||dz  k    r|d|dz   …         }|dfS )zGTruncate at last newline within max_chars. Returns (preview, has_more).FNÚ
é   é   T)r"   Úrfind)r.   r/   Ú	truncatedÚlast_nls       r   Úgenerate_previewr7   R   sg   € å
ˆ7�|„|�yÒ Ð Ø˜ˆ~ÐØ˜
˜˜
Ô#€IØ�oŠo˜dÑ#Ô#€GØ�˜a‘ÒÐØ˜l˜w¨™{˜lÔ+ˆ	Ø�dˆ?Ðr-   c                 óh   — t           | vrt           S dt          j        ¦   «         j        dd…         › �S )z=Return a heredoc delimiter that doesn't collide with content.ÚHERMES_PERSIST_Né   )ÚHEREDOC_MARKERÚuuidÚuuid4Úhex)r.   s    r   Ú_heredoc_markerr?   ]   s3   € å˜WÐ$Ð$ÝÐØ3�TœZ™\œ\Ô-¨b¨q¨bÔ1Ð3Ð3Ð3r-   Úremote_pathc                 óü   — t           j                             |¦  «        }dt          j        |¦  «        › dt          j        |¦  «        › �}|                     |d| ¬¦  «        }|                     dd¦  «        dk    S )uÙ  Write content into the sandbox via env.execute(). Returns True on success.

    Pushes ``content`` through stdin rather than embedding it in the command
    string. Linux's ``MAX_ARG_STRLEN`` caps any single argv element at 128 KB
    (32 * PAGE_SIZE), so the previous heredoc-in-the-command-string approach
    silently failed with ``OSError: [Errno 7] Argument list too long`` for any
    tool result over ~128 KB â€” exactly the case persistence exists to handle.
    Routing through stdin removes that ceiling on local + ssh (``_stdin_mode
    == "pipe"``); remote backends with ``_stdin_mode == "heredoc"`` keep their
    existing API-body sized limit, which is orders of magnitude larger than
    the exec-arg ceiling.
    z	mkdir -p z
 && cat > é   )ÚtimeoutÚ
stdin_dataÚ
returncoder3   r   )ÚosÚpathÚdirnameÚshlexÚquoteÚexecuteÚget)r.   r@   r   Ústorage_dirÚcmdÚresults         r   Ú_write_to_sandboxrP   d   sp   € õ ”'—/’/ +Ñ.Ô.€KØ
T•e”k +Ñ.Ô.Ð
TÐ
T½%¼+ÀkÑ:RÔ:RÐ
TÐ
T€CØ�[Š[˜ b°Wˆ[Ñ=Ô=€FØ�:Š:�l AÑ&Ô&¨!Ò+Ð+r-   ÚpreviewÚhas_moreÚoriginal_sizeÚ	file_pathc                 óä   — |dz  }|dk    r
|dz  d›d�}n|d›d�}t           › d�}|d|d›d|› d	�z  }|d
|› d�z  }|dz  }|dt          | ¦  «        › d�z  }|| z  }|r|dz  }|dt          › �z  }|S )z/Build the <persisted-output> replacement block.i   z.1fz MBz KBr1   z This tool result was too large (Ú,z characters, z).
zFull output saved to: zZUse the read_file tool with offset and limit to access specific sections of this output.

zPreview (first z	 chars):
z
...)ÚPERSISTED_OUTPUT_TAGr"   ÚPERSISTED_OUTPUT_CLOSING_TAG)rQ   rR   rS   rT   Úsize_kbÚsize_strÚmsgs          r   Ú_build_persisted_messager\   w   sÞ   € ð ˜dÑ"€GØ�$‚€Ø ‘nÐ-Ð-Ð-Ð-ˆˆàÐ&Ð&Ð&Ð&ˆå!Ð
%Ð
%Ð
%€CØÐZ¨mÐZÐZÐZÈHÐZÐZÐZÑZ€CØÐ1 IÐ1Ð1Ð1Ñ1€CØÐiÑi€CØÐ5�S ™\œ\Ð5Ð5Ð5Ñ5€CØˆ7�N€CØð Øˆw‰ˆØÐ.Õ,Ð.Ð.Ñ.€CØ€Jr-   Ú	tool_nameÚconfigÚ	thresholdc                 ó¢  — |�|n|                      |¦  «        }|t          d¦  «        k    r| S t          | ¦  «        |k    r| S t          |¦  «        }|› dt	          |¦  «        › �}t          | |j        ¬¦  «        \  }	}
|��	 t          | ||¦  «        rJt           	                    d||t          | ¦  «        |¦  «         t          |	|
t          | ¦  «        |¦  «        S n3# t          $ r&}t                               d||¦  «         Y d}~nd}~ww xY wt           	                    d|t          | ¦  «        ¦  «         |	› dt          | ¦  «        d	›d
�S )aì  Layer 2: persist oversized result into the sandbox, return preview + path.

    Writes via env.execute() so the file is accessible from any backend
    (local, Docker, SSH, Modal, Daytona). Falls back to inline truncation
    if write fails or no env is available.

    Args:
        content: Raw tool result string.
        tool_name: Name of the tool (used for threshold lookup).
        tool_use_id: Unique ID for this tool call (used as filename).
        env: The active BaseEnvironment instance, or None.
        config: BudgetConfig controlling thresholds and preview size.
        threshold: Explicit override; takes precedence over config resolution.

    Returns:
        Original content if small, or <persisted-output> replacement.
    NÚinfr   )r/   z4Persisted large tool result: %s (%s, %d chars -> %s)zSandbox write failed for %s: %szDInline-truncating large tool result: %s (%d chars, no sandbox write)z 

[Truncated: tool response was rV   z3 chars. Full output could not be saved to sandbox.])Úresolve_thresholdÚfloatr"   r   r,   r7   Úpreview_sizerP   r   Úinfor\   r   Úwarning)r.   r]   r   r   r^   r_   Úeffective_thresholdrM   r@   rQ   rR   r   s               r   Úmaybe_persist_tool_resultrh   �   s�  € ð2 (1Ð'<˜)˜)À&×BZÒBZÐ[dÑBeÔBeÐà�e E™lœlÒ*Ð*Øˆå
ˆ7�|„|Ð*Ò*Ð*Øˆå& sÑ+Ô+€KØ ÐGÐGÕ#8¸Ñ#EÔ#EÐGÐG€KÝ(¨¸FÔ<OÐPÑPÔPÑ€GˆXà
€ð	PÝ  ¨+°sÑ;Ô;ð ^Ý—’ØJØ˜{­C°©L¬L¸+ñô ð õ 0°¸Å3ÀwÁ<Ä<ÐQ\Ñ]Ô]Ð]ð^øõ ð 	Pð 	Pð 	PÝ�NŠNÐ<¸kÈ3ÑOÔOÐOÐOÐOÐOÐOÐOøøøøð	Pøøøõ ‡K‚KØNØ•3�w‘<”<ñô ð ð
 ð 	7ð 	7Ý),¨W©¬ÐAð	7ð 	7ð 	7ðs   ÂAC Ã
DÃ)D
Ä
DÚtool_messagesc           	      óN  — g }d}t          | ¦  «        D ]O\  }}|                     dd¦  «        }t          |¦  «        }||z  }t          |vr|                     ||f¦  «         ŒP||j        k    r| S |                     d„ d¬¦  «         |D ]™\  }	}||j        k    r nˆ| |	         }|d         }|                     dd|	› �¦  «        }
t          |t          |
||d¬	¦  «        }||k    r>||z  }|t          |¦  «        z  }|| |	         d<   t           
                    d
|
|¦  «         Œš| S )a"  Layer 3: enforce aggregate budget across all tool results in a turn.

    If total chars exceed budget, persist the largest non-persisted results
    first (via sandbox write) until under budget. Already-persisted results
    are skipped.

    Mutates the list in-place and returns it.
    r   r.   Ú c                 ó   — | d         S )Nr3   © )Úxs    r   ú<lambda>z%enforce_turn_budget.<locals>.<lambda>ä   s
   €  ! A¤$€ r-   T)ÚkeyÚreverseÚtool_call_idÚbudget_)r.   r]   r   r   r^   r_   z7Budget enforcement: persisted tool result %s (%d chars))Ú	enumeraterL   r"   rW   ÚappendÚturn_budgetÚsortrh   Ú_BUDGET_TOOL_NAMEr   re   )ri   r   r^   Ú
candidatesÚ
total_sizeÚir[   r.   ÚsizeÚidxr   Úreplacements               r   Úenforce_turn_budgetr   Ë   sx  € ð €JØ€JÝ˜MÑ*Ô*ð )ð )‰ˆˆ3Ø—'’'˜) RÑ(Ô(ˆÝ�7‰|Œ|ˆØ�dÑˆ
Ý wÐ.Ð.Ø×Ò˜q $˜iÑ(Ô(Ð(øà�VÔ'Ò'Ð'ØÐà‡O‚O˜˜°€OÑ5Ô5Ð5àð ð ‰	ˆˆTØ˜Ô+Ò+Ð+ØˆEØ˜CÔ ˆØ�i”.ˆØ—g’g˜n¨o¸¨o¨oÑ>Ô>ˆå/ØÝ'Ø#ØØØð
ñ 
ô 
ˆð ˜'Ò!Ð!Ø˜$ÑˆJØ�#˜kÑ*Ô*Ñ*ˆJØ,7ˆM˜#Ô˜yÑ)Ý�KŠKØIØ˜Tñô ð øð
 Ðr-   )%Ú__doc__r$   ÚloggingrF   ÚrerI   r<   Útools.budget_configr   r   r   Ú	getLoggerÚ__name__r   rW   rX   r   r;   rx   Úcompiler   r#   r   r   r,   ÚintÚtupleÚboolr7   r?   rP   r\   rc   rh   ÚlistÚdictr   rm   r-   r   ú<module>rŒ      s‚  ððð ð0 €€€Ø €€€Ø 	€	€	€	Ø 	€	€	€	Ø €€€Ø €€€ðð ð ð ð ð ð ð ð ð ð 
ˆÔ	˜8Ñ	$Ô	$€Ø+Ð Ø4Ð Ø#€Ø%€Ø,Ð Ø * ¤
Ð+>Ñ ?Ô ?Ð ØÐ ð ð ð ð ð ð  sð ¨sð ð ð ð ð$ 5Oð ð ˜cð ¨cð ÐSXÐY\Ð^bÐYbÔScð ð ð ð ð4˜Sð 4 Sð 4ð 4ð 4ð 4ð,˜sð ,°ð ,¸dð ,ð ,ð ,ð ,ð&Øðàðð ðð ð	ð
 	ðð ð ð ð: 	Ø)Ø$(ð8ð 8Øð8àð8ð ð8ð
 ð8ð �U‰{˜TÑ!ð8ð 	ð8ð 8ð 8ð 8ðz 	Ø)ð3ð 3Ø˜”:ð3ð ð3ð 
ˆ$„Zð	3ð 3ð 3ð 3ð 3ð 3r-   