
    Pmj9                        d Z ddlZddlmZ ddlmZmZmZ  ed           G d d                      Zd%d
e	de	de
ddfdZd
e	de
de
fdZdedeeef         fdZdddde	de	dedz  de
de
de
defdZdeeeef                  deeeef                  fdZdeeeef                  dz  deeeef                  fdZdeeeef                  deeeef                  defdZdeeeef                  de
dee         fdZdd	dd	d deeeef                  deeeef                  dz  d!ede
dedz  d"e
defd#Z	 	 	 d&deeeef                  d!ede
dedz  deeeef                  f
d$ZdS )'a  Anthropic prompt caching strategy.

The default layout uses 4 cache_control breakpoints: the static system
prefix, the end of the system prompt, and the last 2 non-system messages.
When a static system prefix is unavailable, it falls back to one system
breakpoint plus the last 3 messages. All markers use the same TTL (5m or 1h).
This preserves intra-session caching while allowing new sessions to reuse the
stable system-prompt prefix.

Pure functions -- no class state, no AIAgent dependency.
    N)	dataclass)AnyDictListT)frozenc                   |    e Zd ZU dZeeeef                  ed<   eeeef                  ed<   e	de
fd            ZdS )PromptCachePlanzARequest-local message and tool sections with their cache markers.messagestoolsreturnc                 6    t          | j        | j                  S )zWire-visible cache markers in this plan (computed on demand).

        Only tests consume this; keeping it lazy avoids walking every
        message part and tool schema on the per-request hot path.
        )_count_cache_markersr
   r   )selfs    :/home/thesage/.hermes/hermes-agent/agent/prompt_caching.pymarker_countzPromptCachePlan.marker_count   s     $DM4:>>>    N)__name__
__module____qualname____doc__r   r   strr   __annotations__propertyintr    r   r   r	   r	      ss         KK4S>""""S#X?c ? ? ? X? ? ?r   r	   Fmsgcache_markernative_anthropicr   c                    |                      dd          }|                      d          }|dk    r	|r|| d<   dS ||dk    r|dk    r|sdS |dk    r|sdS || d<   dS t          |t                    rd||d	g| d<   dS t          |t                    r&|r&|d
         }t          |t                    r||d<   dS dS dS dS )zFAdd cache_control to a single message, handling all format variations.role contenttoolcache_controlN	assistanttexttyper&   r$   )get
isinstancer   listdict)r   r   r   r    r"   lasts         r   _apply_cache_markerr/   #   s!   7762Dggi  Gv~~*~  ,O'R--6>>"2>
 F;'7 F+O'3 W|LL
I 	'4   1W 1r{dD!! 	1$0D!!!1 1 1 1	1 	1r   c                     |rdS |                      d          }||dk    rdS t          |t                    r*t          |          ot          |d         t                    S t          |t
                    S )u  True if a marker on this message is actually honored by the provider.

    On the native Anthropic layout every message works (top-level markers are
    relocated by the adapter). On the envelope layout (OpenRouter et al.) only
    markers inside content parts are honored: empty-content messages (e.g.
    assistant turns that are pure tool_calls) and empty tool messages would
    receive a top-level marker the provider ignores — wasting one of the four
    breakpoints. Skip those so the breakpoints land on messages that count.
    Tr"   Nr!   Fr)   )r*   r+   r,   boolr-   r   )r   r   r"   s      r   _can_carry_markerr2   H   sw      tggi  G'R--u'4   ?
 G}}>GBK!>!>>gs###r   ttlc                 $    ddi}| dk    rd|d<   |S )zCBuild a cache_control marker dict for the given TTL ('5m' or '1h').r(   	ephemeral1hr3   r   )r3   markers     r   _build_markerr8   `   s#    $k2F
d{{uMr   )mark_suffixfallback_to_wholemessagestatic_system_prefixr9   r:   c                r   |                      d          }t          |t                    rv|rtt          |t                    r_|                    |          rJ|t	          |          d         }|rd|d}|r||d<   d||d|g| d<   |rdndS t          | ||	           dS |sd
S t          | ||	           dS )u  Mark the static system prefix (and optionally the full prompt).

    The system prompt remains one stored string. Splitting it only in the
    outgoing request keeps session persistence and non-Anthropic transports
    unchanged while making the stable prefix independently cacheable.

    ``mark_suffix=False`` is the tool-cache-plan layout: only the static
    prefix carries a marker, the volatile suffix rides unmarked (its
    breakpoint budget is spent on the tools array instead).

    ``fallback_to_whole=False`` skips marking entirely when the prefix
    split is not possible (no prefix, mismatched prefix, non-string
    content) instead of marking the whole message.

    When the prompt IS exactly the static prefix (empty suffix), the whole
    message is marked as a single block — never a two-part split with an
    empty text block, which Anthropic rejects.

    Returns the number of markers applied (0, 1, or 2).
    r"   Nr&   )r(   r&   r$   r'         r   r   )r*   r+   r   
startswithlenr/   )	r;   r   r<   r   r9   r:   r"   suffixsuffix_parts	            r   _apply_system_cache_markersrE   h   s   : kk)$$G'--  w$$ 344	 122334 	+)/ @ @K </;O, #0%1 
 "GI $*11* 	G\DTUUUUq q@PQQQQ1r   api_messagesc                    | D ]}t          |t                    s|                    dd           |                    d          }t          |t                    sZt          d |D                       rd |D             }||d<   |oWt          d |D                       o>t          |          dk    p+|                    d          d	k    ot          |          d
k    }|r"d                    d |D                       |d<   | S )uL  Remove ``cache_control`` markers and undo decoration-produced list shapes.

    Used before re-applying decoration after a mid-turn provider failover so
    the mutated, undecorated shape (image shrink / ASCII cleanup / etc.) is
    preserved while markers match the *new* provider's cache policy (#72626).

    Flattening back to a plain string is restricted to the exact shapes
    :func:`apply_anthropic_cache_control` produces from string content —
    a single ``{"type": "text"}`` part, or the two-part ``[static, volatile]``
    system split — so the ``""``-join is provably byte-exact. Organic
    multi-part text (merged user turns, imported transcripts) and parts
    carrying extra keys (``citations`` etc.) keep their structure; only
    per-part markers are removed. Marker removal is copy-on-write on the
    part dicts: content parts may alias the persistent conversation history
    (the per-call copy is shallow), and stripping must never rewrite the
    stored transcript.

    Mutates the top-level message dicts of ``api_messages`` in place and
    returns the same list.
    r$   Nr"   c              3   H   K   | ]}t          |t                    od |v V  dS )r$   Nr+   r-   .0parts     r   	<genexpr>z0strip_anthropic_cache_control.<locals>.<genexpr>   s6      VVdz$%%A/T*AVVVVVVr   c                     g | ];}t          |t                    r"d |v rd |                                D             n|<S )r$   c                 &    i | ]\  }}|d k    ||S )r$   r   )rK   kvs      r   
<dictcomp>z<strip_anthropic_cache_control.<locals>.<listcomp>.<dictcomp>   s(    GGG$!Q!2F2FA2F2F2Fr   )r+   r-   itemsrJ   s     r   
<listcomp>z1strip_anthropic_cache_control.<locals>.<listcomp>   sf         dD)).=.E.E HG$**,,GGGG  r   c              3     K   | ]}t          |t                    oh|                    d d          dk    oNt          |                    d          t                    o&t	          |                                          d dhk    V  dS )r(   r&   N)r+   r-   r*   r   setkeysrJ   s     r   rM   z0strip_anthropic_cache_control.<locals>.<genexpr>   s       +
 +

 	 tT"" 5((F25488F++S115 DIIKK  VV$44	+
 +
 +
 +
 +
 +
r   r?   r    systemr>   r!   c              3   &   K   | ]}|d          V  dS )r&   Nr   rJ   s     r   rM   z0strip_anthropic_cache_control.<locals>.<genexpr>   s&      $F$FdT&\$F$F$F$F$F$Fr   )	r+   r-   popr*   r,   anyallrB   join)rF   r   r"   decoration_shapes       r   strip_anthropic_cache_controlr_      sX   .  G G#t$$ 	&&&'')$$'4(( 	VVgVVVVV 	%  $	  G %C	N" 	
s +
 +

  +
 +
 +
 (
 (
 	
 LLA C8+AG0A 	  	GWW$F$Fg$F$F$FFFC	Nr   r   c                     t          j        | pg           }|D ]-}t          |t                    r|                    dd           .|S )zBReturn copied tools without request-local Anthropic cache markers.r$   N)copydeepcopyr+   r-   rZ   )r   cleanedr#   s      r   "strip_anthropic_tool_cache_controlrd      sP    mEKR((G , ,dD!! 	,HH_d+++Nr   r
   c                     t          d | D                       }|t          d | D                       z  }|t          d |D                       z   S )z=Count the wire-visible cache markers in a request-local plan.c              3   L   K   | ]}t          |t                    rd |v dV   dS r$   r?   NrI   )rK   r;   s     r   rM   z'_count_cache_markers.<locals>.<genexpr>   sM        gt$$ *9G)C)C 	
)C)C)C)C r   c              3      K   | ]g}t          |t                    rPt          |                    d           t                    r(|d          D ]}t          |t                    rd|v dV   hdS )r"   r$   r?   N)r+   r-   r*   r,   )rK   r;   rL   s      r   rM   z'_count_cache_markers.<locals>.<genexpr>   s        gt$$ *4GKK	4J4JD)Q)Q I&	  dD!!
 '6&=&=	 	
 '>&=&=&=&= r   c              3   L   K   | ]}t          |t                    rd |v dV   dS rg   rI   )rK   r#   s     r   rM   z'_count_cache_markers.<locals>.<genexpr>   sG        z$55:IT:Q:Q:Q:Q:Q:Q r   )sum)r
   r   counts      r   r   r      s          E
 
S       E 3        r   c                   g }d}|t          |           k     r5| |         }t          |t                    r|                    d          dk    r|dz  }P|                    d          dk    r|                    d          r|dz   }|}|t          |           k     rO| |         }t          |t                    r|                    d          dk    rn|dz  }|t          |           k     O||k    r0|dz
  }t	          | |         |          r|                    |           |}!|                    d          dk    rd|t          |           k     rO| |         }t          |t                    r|                    d          dk    rn|dz  }|t          |           k     O|                    d          dk    r|dz   t          |           k     r|dz  }|                    d          dk    r|                    d	          d
v r|dz  }t	          ||          r|                    |           |dz  }|t          |           k     5|S )z<Select legal ends of completed tool runs and ordinary turns.r   r    rX   r?   r%   
tool_callsr#   userr"   )Nr!   )rB   r+   r-   r*   r2   append)	r
   r   	endpointsindexr;   result_start
result_endresultendpoints	            r   '_completed_transaction_endpoint_indexesrv      s{    IE
#h--

5/'4(( 	GKK,?,?8,K,KQJE;;v+--'++l2K2K- 19L%Js8}},,!*-!&$// 6::f3E3E3O3Oa
	 s8}},,
 L((%>$Xh%79IJJ /$$X...E;;v&((#h--''!%!&$// 6::f3E3E3O3O
	 #h--''
 ;;v&((UQYX-F-FQJE KK;..I&&*44QJEW&677 	$U###
U #h--

V r   5m)	cache_ttlr   r<   direct_native_tool_cacherx   ry   c                @   t          j        | pg           }t          |           t          |          }|r|s$t	          ||||          }t          ||          S t          |          }	|rUt          |d         t                    r:|d         	                    d          dk    rt          |d         |	|ddd           t          |	          |d	         d
<   t          |d          dd         D ]}
t          ||
         |	d           t          ||          S )zCBuild isolated cache sections for one resolved request destination.)rx   r   r<   )r
   r   r   r    rX   TF)r   r9   r:   r)   r$   r@   N)ra   rb   r_   rd   apply_anthropic_cache_controlr	   r8   r+   r-   r*   rE   rv   r/   )rF   r   rx   r   r<   ry   r
   planned_toolsplanned_messagesr7   ru   s              r   build_prompt_cache_planr   +  so    }\/R00H!(+++6u==M# O= O8-!5	
 
 
 (8NNNN9%%F
x{D))
 QKOOF##x// 	$QK !#	
 	
 	
 	
 *.fM"o&;   	cc O O 	HX.NNNNNHMBBBBr   c                   	 | s| S t          |           	t          |          }d}	d                             d          dk    r6t          j        	d                   	d<   t          	d         ||          }d|z
  }	fdt          t          	                    D             }|| d         D ]7}t          j        	|                   	|<   t          	|         |           8	S )a  Apply Anthropic cache-control markers to API messages.

    When ``static_system_prefix`` exactly matches the beginning of a string
    system prompt, it receives an early marker and the full system prompt gets
    a trailing marker. The remaining two markers target the latest cacheable
    non-system messages. Without that prefix, the legacy system-and-3 layout
    is retained.

    Returns:
        Shallow copy of message list with selective deep copies of modified messages.
    r   r    rX   r@      c                     g | ]:}|                              d           dk    !t          |                   8|;S )r    rX   r@   )r*   r2   )rK   ir
   r   s     r   rT   z1apply_anthropic_cache_control.<locals>.<listcomp>  sX       A;??6""h..hqk<LMMM / 	
...r   N)	r,   r8   r*   ra   rb   rE   rangerB   r/   )
rF   rx   r   r<   r7   breakpoints_used	remainingnon_sysidxr
   s
     `      @r   r|   r|   \  s*   "  L!!H9%%F{v(**mHQK006QK -	
 
 
 $$I    s8}}%%  G 	z{{# V Vhsm44HSM6DTUUUUUOr   )F)rw   FN)r   ra   dataclassesr   typingr   r   r   r	   r-   r1   r/   r2   r   r8   r   rE   r_   rd   r   rv   r   r|   r   r   r   <module>r      s  
 
  ! ! ! ! ! ! " " " " " " " " " " $? ? ? ? ? ? ? ? "1 "1T "1 "1 "1Z^ "1 "1 "1 "1J$4 $4 $D $ $ $ $0s tCH~     "; ; ;;; *;
 ; ; ; 	; ; ; ;|2tCH~&2	$sCx.2 2 2 2jd4S>.BT.I dSWX[]`X`SaNb    4S#X#7 T#s(^@T Y\    &14S>"19=1	#Y1 1 1 1p "'+%*.C .C .CtCH~&.CS#X$&.C 	.C
 .C *.C #.C .C .C .C .Cf "'+	- -tCH~&-- - *	-
 
$sCx.- - - - - -r   