
    sjlL                     @   d Z ddlZddlmZ ddlmZmZmZ ddl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 eh d          ZdedefdZddddedz  dedede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List)find_stable_prefixT)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    8/home/agent/.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                    rH|dk    r6t          |          }|%d	||d
d	|t	          |          d         dg| d<   dS 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usertexttyper(   r%   r*   r(   )get
isinstancer   r   lenlistdict)r   r   r   r!   r#   stable_prefixlasts          r   _apply_cache_markerr4   %   s   7762Dggi  Gv~~*~  ,O'R--6>>"2>
 F;'7 F+O'3 6>>.w77M( !' -)5 
 $WS5G5G5H5H-IJJ"I 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.   r0   boolr1   r   )r   r   r#   s      r   _can_carry_markerr7   [   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1hr8   r   )r8   markers     r   _build_markerr=   s   s#    $k2F
d{{uMr   >   opencode-goopencode-zenalibabaopencodemodelc                 2    d| pd                                 v S )zTrue when ``model`` names a Qwen-family model (case-insensitive).

    Shared by the TTL clamp below and
    ``agent_runtime_helpers.anthropic_prompt_cache_policy`` so the
    cache-policy opt-in and the clamp can never desync (#84733).
    qwenr"   )lower)rB   s    r   is_qwen_modelrF      s     ekr((****r   r"   )rB   providerrG   c                z    | dk    r| pdS t          |          rdS |pd                                t          v rdS dS )a  Clamp a requested cache TTL to what the destination route supports.

    Qwen/Alibaba context caching documents an explicit five-minute window
    (renewed on hit); the Anthropic ``1h`` tier is ignored/rejected there,
    so a configured ``1h`` regresses to ``5m`` instead of shipping a marker
    the provider drops and creating a false 1h-cache expectation (#84733).
    All other caching routes keep the requested TTL.

    ``None`` (caching active with no explicit tier) resolves to ``5m``.
    r;   5mr"   )rF   rE   ALIBABA_FAMILY_PROVIDERS)r8   rB   rG   s      r   effective_cache_ttlrK      sR      d{{{dU tB#;;;t4r   )mark_suffixfallback_to_wholemessagestatic_system_prefixrL   rM   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   
startswithr/   r4   )	rN   r   rO   r   rL   rM   r#   suffixsuffix_parts	            r   _apply_system_cache_markersrW      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 ]z}t          |t                    s|                    dd           |                    d          }t          |t                    sZ|                    d          dk    o\t          |          dk    oIt          |d         t                    o.t          |d         t                    od|d         v o	d|d         v}t          d	 |D                       rd
 |D             }||d<   |oYt          d |D                       o@t          |          dk    p-|                    d          dk    ot          |          dk    p|}|r"d                    d |D                       |d<   || S )u^  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, the two-part ``[static, volatile]``
    system split, or the two-part builder-declared skill split (recognised
    by its marker-on-the-first-part shape, so flattening never depends on
    the prefix registry still holding the entry) — 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 can alias caller-held message lists (the main
    send path now hands structurally-cloned copies via
    _clone_message_for_send, but other callers may pass shallow copies),
    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#   r!   r'   rQ   r   rR   c              3   H   K   | ]}t          |t                    od |v V  dS r%   Nr.   r1   .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   )r^   kvs      r   
<dictcomp>z<strip_anthropic_cache_control.<locals>.<listcomp>.<dictcomp>  s(    GGG$!Q!2F2FA2F2F2Fr   )r.   r1   itemsr]   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.   r1   r-   r   setkeysr]   s     r   r`   z0strip_anthropic_cache_control.<locals>.<genexpr>!  s       +
 +

 	 tT"" 5((F25488F++S115 DIIKK  VV$44	+
 +
 +
 +
 +
 +
r   systemr"   c              3   &   K   | ]}|d          V  dS )r(   Nr   r]   s     r   r`   z0strip_anthropic_cache_control.<locals>.<genexpr>-  s&      $F$FdT&\$F$F$F$F$F$Fr   )	r.   r1   popr-   r0   r/   anyalljoin)rX   r   r#   skill_split_shapedecoration_shapes        r   strip_anthropic_cache_controlrs      s   6  *G *G#t$$ 	&&&'')$$'4(( 	 GGFOOv% 2G!271:t,,2 71:t,,2  71:-	2
  wqz1 	 VVgVVVVV 	%  $	  G %C	N" 

s +
 +

  +
 +
 +
 (
 (
 

 LLA !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.   r1   rm   )r   cleanedr$   s      r   "strip_anthropic_tool_cache_controlrx   1  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%   rR   Nr\   )r^   rN   s     r   r`   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%   rR   N)r.   r1   r-   r0   )r^   rN   r_   s      r   r`   z'_count_cache_markers.<locals>.<genexpr>A  s        gt$$ *4GKK	4J4JD)Q)Q I&	  dD!!
 '6&=&=	 	
 '>&=&=&=&= r   c              3   L   K   | ]}t          |t                    rd |v dV   dS r{   r\   )r^   r$   s     r   r`   z'_count_cache_markers.<locals>.<genexpr>H  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!   rk   rR   r&   
tool_callsr$   r'   r#   )Nr"   )r/   r.   r1   r-   r7   append)	r   r   	endpointsindexrN   result_start
result_endresultendpoints	            r   '_completed_transaction_endpoint_indexesr   M  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   rI   )	cache_ttlr   rO   direct_native_tool_cacher   r   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.)r   r   rO   )r   r   r   r!   rk   TF)r   rL   rM   r,   r%   rS   N)ru   rv   rs   rx   apply_anthropic_cache_controlr
   r=   r.   r1   r-   rW   r   r4   )rX   r   r   r   rO   r   r   planned_toolsplanned_messagesr<   r   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          |          }t                    D ]\  }}t          |t                    s|                    d          }d|v p-t          |t                     ot          d |D                       }|r&t          t	          |          g          d         |<   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 )u  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.

    Idempotent: pre-existing ``cache_control`` markers are stripped from a
    per-message copy before new ones are placed, so calling this twice (or
    handing it messages a prior call already marked) can never accumulate
    past 4 markers. Only messages that already carry a marker pay the copy
    cost — a shallow top-level copy suffices because
    :func:`strip_anthropic_cache_control` is copy-on-write on content parts —
    and the rest of the copy-on-write contract is unchanged (#90971).

    Returns:
        Shallow copy of message list with selective deep copies of modified messages.
    r#   r%   c              3   H   K   | ]}t          |t                    od |v V  dS r[   r\   r]   s     r   r`   z0apply_anthropic_cache_control.<locals>.<genexpr>  s6      [[4JtT**F$/F[[[[[[r   r   r!   rk   rS      c                     g | ]:}|                              d           dk    !t          |                   8|;S )r!   rk   rS   )r-   r7   )r^   ir   r   s     r   rg   z1apply_anthropic_cache_control.<locals>.<listcomp>  sX       A;??6""h..hqk<LMMM / 	
...r   N)r0   r=   	enumerater.   r1   r-   rn   rs   ru   rv   rW   ranger/   r4   )rX   r   r   rO   r<   r   r   r#   
has_markerbreakpoints_used	remainingnon_sysidxr   s     `          @r   r   r     s   2  L!!H9%%FH%% H H3#t$$ 	'')$$$+ 
w%% \[[SZ[[[[[ 	  	H 8cDDQGHQK{v(**mHQK006QK -	
 
 
 $$I    s8}}%%  G 	z{{# V Vhsm44HSM6DTUUUUUOr   )F)rI   FN)r   ru   dataclassesr   typingr   r   r   agent.prompt_cache_boundaryr   r
   r1   r6   r4   r7   r   r=   	frozensetrJ   rF   rK   r   rW   rs   rx   r   r   r   r   r   r   r   <module>r      s\  
 
  ! ! ! ! ! ! " " " " " " " " " " : : : : : : $? ? ? ? ? ? ? ? 31 31T 31 31 31Z^ 31 31 31 31l$4 $4 $D $ $ $ $0s tCH~     %9 & & &   + + + + + + 	  	t  	
 	   > "; ; ;;; *;
 ; ; ; 	; ; ; ;|FtCH~&F	$sCx.F F F FR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 "'+	C CtCH~&CC C *	C
 
$sCx.C C C C C Cr   