
    i/                     L   d 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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__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/ubuntu/.hermes/hermes-agent/tools/tool_result_storage.py_resolve_storage_dirr   ,   s    
sND99L!! 	88'<>>  8's33:sH&77778  H H HA3GGGGGGGGH s   
A 
A<A77A<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)lenrfind)r   r   	truncatedlast_nls       r   generate_previewr"   <   sg    
7||y  ~

#Iood##Galw{l+	d?    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+   G   s3    W$$3TZ\\-bqb1333r#   remote_pathc           
      *   t          |           }t          j                            |          }dt	          j        |           dt	          j        |           d| d|  d| 
}|                    |d          }|                    dd	          d
k    S )zJWrite content into the sandbox via env.execute(). Returns True on success.z	mkdir -p z
 && cat > z << 'z'
r      )timeout
returncoder   r   )r+   ospathdirnameshlexquoteexecuteget)r   r,   r   markerstorage_dircmdresults          r   _write_to_sandboxr<   N   s    W%%F'//+..K	EK,, 	 	K8P8P 	 	W] 	 		 		 	 
 [[b[))F::lA&&!++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 KBr   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)r=   r>   r?   r@   size_kbsize_strmsgs          r   _build_persisted_messagerH   [   s     d"G$n----&&&&!
%
%
%CZmZZZHZZZZC1I1111CiiC5S\\5555C7NC w.,...CJr#   	tool_nametool_use_idconfig	thresholdc                    ||n|                     |          }|t          d          k    r| S t          |           |k    r| S t          |          }| d| d}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   z.txt)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 rB   z3 chars. Full output could not be saved to sandbox.])resolve_thresholdfloatr   r   r"   preview_sizer<   r   inforH   r   warning)r   rI   rJ   r   rK   rL   effective_thresholdr9   r,   r=   r>   r   s               r   maybe_persist_tool_resultrU   t   s   2 (1'<))&BZBZ[dBeBeeEll**
7||***&s++K 44;444K(F<OPPPGX
	P +s;; ^J{CLL+   03w<<Q\]]]^  	P 	P 	PNN<k3OOOOOOOO	P KKN3w<<  
  	7 	7),WA	7 	7 	7s   7AC 
DC>>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 )Nr    )xs    r   <lambda>z%enforce_turn_budget.<locals>.<lambda>   s
    !A$ r#   T)keyreversetool_call_idbudget_)r   rI   rJ   r   rK   rL   z7Budget enforcement: persisted tool result %s (%d chars))	enumerater7   r   rC   appendturn_budgetsortrU   _BUDGET_TOOL_NAMEr   rR   )rV   r   rK   
candidates
total_sizeirG   r   sizeidxrJ   replacements               r   enforce_turn_budgetrl      sx    JJM** ) )3'')R((7||d
w..q$i(((V'''OOO555  	T+++EC i.ggnooo>>/'#
 
 
 '!!$J#k***J,7M#y)KKIT  
 r#   )__doc__loggingr1   r4   r(   tools.budget_configr   r   r   	getLogger__name__r   rC   rD   r   r'   re   strr   inttupleboolr"   r+   r<   rH   rP   rU   listdictrl   rZ   r#   r   <module>rx      s5   0  				            
	8	$	$+ 4 #%,       5O  c c SXY\^bYbSc    4S 4S 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#   