
    Jj                       d Z ddlm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 ddl	m
Z
 ddlmZmZmZ ddlmZ  ej        e          ZdZd	e d
Zd0dZ G d d          Zd1dZd1dZd2dZd3dZdddddd4d(Zd)d*d5d.Zg d/ZdS )6u  Context compression — extract the AIAgent methods that drive summarisation.

Three concerns live here:

* :func:`check_compression_model_feasibility` — startup probe of the
  configured auxiliary compression model.  Warns when the aux context
  window can't fit the main model's compression threshold; auto-lowers
  the session threshold when possible; hard-rejects auxes below
  ``MINIMUM_CONTEXT_LENGTH``.

* :func:`replay_compression_warning` — re-emit a stored warning through
  the gateway ``status_callback`` once it's wired up (the callback is
  set after :class:`AIAgent` construction).

* :func:`compress_context` — the actual compression call.  Runs the
  configured compressor, splits the SQLite session, rotates the
  session_id, notifies plugin context engines / memory providers, and
  returns the compressed message list and freshly-built system prompt.

* :func:`try_shrink_image_parts_in_messages` — image-too-large recovery
  helper that re-encodes ``data:image/...;base64,...`` parts at a smaller
  size so retries can fit under provider ceilings (Anthropic's 5 MB).

``run_agent`` keeps thin wrappers for each so existing call sites
(``self._compress_context(...)``) keep working.  Tests that exercise
these paths see no behavioural change.
    )annotationsN)datetime)Path)AnyOptionalTuple)estimate_request_tokens_roughzCompacting contextu   🗜️ u:    — summarizing earlier conversation so I can continue...agentr   returnstrc           
         ddl }dt          j                     d |j                     dt	          |           ddt          j                    j        dd          S )	a  Build a unique holder id for the lock: pid:tid:agent-instance:uuid.

    The pid+tid prefix lets ops tell crashed/abandoned holders apart from
    live ones (expiry-based recovery uses the timestamp, but ``holder``
    is what shows up in diagnostics + log lines). The agent instance id
    and a per-acquire uuid disambiguate two co-resident agents on the
    same thread (background_review forks run on a worker thread, but
    on machines where compression itself dispatches to a thread pool
    we want each acquire to be unique).
    r   Nzpid=z:tid=z:agent=xz:nonce=   )	threadingosgetpid	get_identiduuiduuid4hex)r
   r   s     D/home/rurouni/.hermes/hermes-agent/agent/conversation_compression.py_compression_lock_holderr   7   s{     	)ry{{ 	) 	)#	#%%	) 	)U))	) 	) *,,"2A2&	) 	)    c                  2    e Zd Z	 dddZddZddZddZdS )_CompressionLockLeaseRefresherNdbr   
session_idr   holderttl_secondsfloatrefresh_interval_secondsfloat | Noner   Nonec                   || _         || _        || _        || _        |!t	          dt          d|dz                      }t	          dt          |                    | _        t	          dt          | j        | j        z                      | _	        t          j                    | _        t          j        | j        dd          | _        d S )	N      ?g      N@g       @g?   zcompression-lock-refreshT)targetnamedaemon)_db_session_id_holder_ttl_secondsmaxminr!   _refresh_interval_secondsint_max_consecutive_failuresr   Event_stopThread_run_thread)selfr   r   r   r    r"   s         r   __init__z'_CompressionLockLeaseRefresher.__init__L   s     %'#+'*3D+:K0L0L'M'M$),S%8P2Q2Q)R)R& *-s4$t'EEFF*
 *
& _&&
 '9+
 
 
r    '_CompressionLockLeaseRefresher'c                8    | j                                          | S N)r8   startr9   s    r   r>   z$_CompressionLockLeaseRefresher.startj   s    r   c                    | j                                          | j                                        r7t	          j                    | j        ur| j                            d           d S d S d S )Nr&   )timeout)r5   setr8   is_aliver   current_threadjoinr?   s    r   stopz#_CompressionLockLeaseRefresher.stopn   sp    
 <  "" 	+y'?'A'A'U'ULc*****	+ 	+'U'Ur   c                   d}| j                             | j                  s	 | j                            | j        | j        | j                  }n4# t          $ r'}t          
                    d|           d}Y d }~nd }~ww xY w|rd}|dz  }|| j        k    r#t          
                    d|| j                   d S | j                             | j                  d S d S )Nr   r    z#compression lock refresh raised: %sFr'   zZcompression lock refresh failed %d times in a row; stopping lease refresher for session %s)r5   waitr1   r+   refresh_compression_lockr,   r-   r.   	Exceptionloggerdebugr3   )r9   consecutive_failures	refreshedexcs       r   r7   z#_CompressionLockLeaseRefresher._runx   s!     !*//$"@AA 	" H==$L $ 1 >  		
  " " "BCHHH!						"  '($ A% #t'EEE>($*:  
 ) *//$"@AA 	 	 	 	 	s   ,A 
BA<<Br=   )r   r   r   r   r   r   r    r!   r"   r#   r   r$   )r   r;   r   r$   )__name__
__module____qualname__r:   r>   rF   r7    r   r   r   r   K   sn         26
 
 
 
 
<   + + + +     r   r   r$   c                z   | j         sdS 	 ddlm}m}m} ddlm}m} 	  |d          \  }}}}}n# t          $ r d}Y nw xY w |d| 	                                          \  }}	||	sU |d|          \  }
}}|
C|rA|
|}	}d|v r9|
                    d	          r$|                    dd
          d
         dd         }||	sI|r|dk    rd| d}nd}|| _        |                     |           t                              d           dS t!          t#          |dd                    }t#          |dd          }t%          |          rt'          |t                     sdnt!          |pd          } ||	||t#          | dd          |r|dk    r|nt#          | dd          | j                  }|r'||k     r!t+          d|	 d|dd|dd|dz   d	          | j        j        }||k     r|}|}|| j        _        | j        j        }|r||z  | j        _        |rt5          ||z  dz            nd}t#          | dd          pd }t#          | dd          pd}|r|dk    r|nd}|s.	 dd!lm}  ||          j        p|}n# t          $ r |pd}Y nw xY w|r| d"| d	n|}|	 d"| d	}d#| d$|dd%| d&|dd'|dd(|dd)|d*}|| _        |                     |           t                              d+|	|||           dS dS # t*          $ r  t          $ r&}t                              d,|           Y d}~dS d}~ww xY w)-au  Warn at session start if the auxiliary compression model's context
    window is smaller than the main model's compression threshold.

    When the auxiliary model cannot fit the content that needs summarising,
    compression will either fail outright (the LLM call errors) or produce
    a severely truncated summary.

    Called during ``AIAgent.__init__`` so CLI users see the warning
    immediately (via ``_vprint``).  The gateway sets ``status_callback``
    *after* construction, so :func:`replay_compression_warning` re-sends
    the stored warning through the callback on the first
    ``run_conversation()`` call.
    Nr   )_resolve_task_provider_model/_try_configured_fallback_for_unavailable_clientget_text_auxiliary_client)MINIMUM_CONTEXT_LENGTHget_model_context_lengthcompression )main_runtime()r'   autou/   ⚠ Configured auxiliary compression provider 'u   ' is unavailable — context compression will drop middle turns without a summary. Check auxiliary.compression in config.yaml and reauthenticate that provider.u   ⚠ No auxiliary LLM provider configured — context compression will drop middle turns without a summary. Run `hermes setup` or set OPENROUTER_API_KEY.uL   No auxiliary LLM provider for compression — summaries will be unavailable.base_urlapi_key&_aux_compression_context_length_configprovider)rc   rd   config_context_lengthrf   custom_providerszAuxiliary compression model z has a context window of ,z$ tokens, which is below the minimum zE required by Hermes Agent.  Choose a compression model with at least i  zK context (set auxiliary.compression.model in config.yaml), or set auxiliary.compression.context_length to override the detected value if it is wrong.d   2   model?)urlparsez (u   ⚠ Compression model z context is z tokens, but the main model z's compression threshold was z2 tokens. Auto-lowered this session's threshold to u    tokens so compression can run.
  To make this permanent, edit config.yaml — either:
  1. Use a larger compression model:
       auxiliary:
         compression:
           model: <model-with-z[+-context>
  2. Lower the compression threshold:
       compression:
         threshold: 0.02du   Auxiliary compression model %s has %d token context, below the main model's compression threshold of %d tokens — auto-lowered session threshold to %d to keep compression working.z4Compression feasibility check failed (non-fatal): %s)compression_enabledagent.auxiliary_clientrW   rX   rY   agent.model_metadatarZ   r[   rK   _current_main_runtimeendswithrsplit_compression_warning_emit_statusrL   warningr   getattrcallable
isinstance_custom_providers
ValueErrorcontext_compressorthreshold_tokenscontext_lengththreshold_percentr2   urllib.parsern   hostnamerM   )r
   rW   rX   rY   rZ   r[   _aux_cfg_provider_client	aux_model	fb_clientfb_modelfb_labelmsgaux_base_url_raw_aux_keyaux_api_keyaux_context	thresholdold_thresholdnew_thresholdmain_ctxsafe_pct_main_model_main_provider_aux_provider_labelrn   _main_label
_aux_labelrP   s                                 r   #check_compression_model_feasibilityr      s    $ t
	
 	
 	
 	
 	
 	
 	
 	
 	
 	

	
 	
 	
 	
 	
 	
 	
 	
	#,H,H,W,W)q!Q 	# 	# 	# "	#554466
 
 
	 >>,[,[!- -)Ix $$$-x	(??x'8'8'='=?(0Q(?(?(B3B3(G%>>  %6&%@%@4)4 4 4 D 
 *-E&s###NN1   F76:r::;; vy"55%l33wJ|UX<Y<Ywbb`cdpdvtv`w`w..!")%1Y[_"`"` ,=  AARV\A\A\''bijoq{}  cA  cA"4

 

 

$  
	;)???2y 2 2(H2 21I2 2 *T1	2 2 2	 	 	 ,=	"" &M'M8EE$5 />H !H, (: ?GNsK(2c9:::BH "%"55<K$UJ;;ArN %):f)D)D "!  
 ' AA555555 ..7G< (' ! A A A*6*@&'''A "!;33.3333  
 &??)<???J8 8 8>8 88 8 !-8 8
 !E8 8 2?N8 8 *278 8   *-E&s###NN, 	 	 	 	 	E #"X     	 
 
 
BC	
 	
 	
 	
 	
 	
 	
 	
 	

s_   L  0 L  ?L  ?C	L  
EL  %I> =L  >JL  JA-L   L:L55L:c                    t          | dd          }|r1| j        r,	 |                     d|           dS # t          $ r Y dS w xY wdS dS )u  Re-send the compression warning through ``status_callback``.

    During ``__init__`` the gateway's ``status_callback`` is not yet
    wired, so ``_emit_status`` only reaches ``_vprint`` (CLI).  This
    method is called once at the start of the first
    ``run_conversation()`` — by then the gateway has set the callback,
    so every platform (Telegram, Discord, Slack, etc.) receives the
    warning.
    rv   N	lifecycle)ry   status_callbackrK   )r
   r   s     r   replay_compression_warningr   a  s     %/
6
6C
 u$ 	!!+s33333 	 	 	DD	   s   4 
AAmessageslistOptional[list]c                `    t          t          | dd                    rt          |          S dS )a  Return the correct flush baseline after a compression boundary.

    Legacy compression rotates to a fresh child session. That child has not
    seen the compacted transcript through the normal same-turn flush path yet,
    so callers must clear ``conversation_history`` to ``None`` and let the next
    persistence call write the whole compacted list.

    In-place compaction is different: ``archive_and_compact()`` has already
    soft-archived the previous active rows and inserted ``messages`` as the new
    active live transcript under the same session id. If the same agent turn
    continues with ``conversation_history=None``, the identity-based flush path
    treats those already-persisted compacted dicts as new and appends them a
    second time, doubling the active context and retriggering compression.

    A shallow copy is intentional: it captures the current compacted dict
    identities as history while allowing later same-turn appends to remain new.
    _last_compaction_in_placeFN)boolry   r   )r
   r   s     r   &conversation_history_after_compressionr   s  s2    $ GE6>>?? H~~4r   original_messages
compressedc                :   t          d |D                       rdS ddlm} t          |           D ]Q}t	          |t
                    r|                    d          dk    r1|                     ||                      dS |                    ddd           dS )	u  Preserve a real user turn when a compressor returns assistant/tool-only context.

    On repeated compaction the protected head decays to the system prompt only,
    the middle summary can land as ``role="assistant"``, and a tool-heavy tail
    can be all assistant/tool — so the compacted transcript can legitimately
    contain zero user messages. Strict chat templates (LM Studio / llama.cpp
    Jinja) then fail with "No user query found in messages" (#55677).

    The restored turn is appended at the END: the guard only runs when
    ``compressed`` currently ends with an assistant/tool message (any existing
    user turn — including a todo-snapshot append — short-circuits the
    ``any()`` check), so appending a user message never creates consecutive
    same-role messages. ``_fresh_compaction_message_copy`` copies the message
    and strips the ``_db_persisted`` marker so the rotation/in-place flush
    still persists the restored row to the new session (#57491).

    If the pre-compression transcript itself carried no user turn at all
    (near-impossible — every real conversation opens with a user request —
    but kept as a defensive backstop), a minimal continuation marker is
    appended instead so strict templates still see a user message.
    c              3  r   K   | ]2}t          |t                    o|                    d           dk    V  3dS )roleuserN)r{   dictget).0r   s     r   	<genexpr>z3_ensure_compressed_has_user_turn.<locals>.<genexpr>  s@      
U
U3:c4  >SWWV__%>
U
U
U
U
U
Ur   Nr   )_fresh_compaction_message_copyr   r   zContinue from the compressed conversation context above. This marker exists because the compacted transcript contained no preserved user turn.r   content)anyagent.context_compressorr   reversedr{   r   r   append)r   r   r   r   s       r    _ensure_compressed_has_user_turnr     s    , 
U
U*
U
U
UUU GGGGGG)**  #t$$ 	6(A(A88==>>>&      r   defaultF)approx_tokenstask_idfocus_topicforcesystem_messager   Optional[int]r   r   Optional[str]r   r   Tuple[list, str]c                 +,-. t          | dd          st          |            d| _        t          |          }t	          t          | dd                    }d}	t
                              d| j        pd||r|dnd| j        |           | 	                    t                     t          | d	d
          +| j        pd.d
,	 t          t          | dd          pd          }
n# t          t          f$ r d}
Y nw xY wt          | dd
          }d
-+q.rnt          |           ,	 +                    .,|
          }nf# t           $ rY}d
,t          | dd
          .k    r6.| _        t
                              d.t'          |          j        |           d}Y d
}~nd
}~ww xY w|s	 +                    .          }n# t           $ r d
}Y nw xY wt
                              d.|           d
,t          | dd
          .k    r..| _        	 |                     d           n# t           $ r Y nw xY wt          | dd
          }|s|                     |          }||fS ,%t3          +.,|
|                                          -dQ+,-.fd}| j        r,	 | j                            |           n# t           $ r Y nw xY w	 | j                            ||||          }n[# t          $ r: 	 | j                            ||          }n# t>          $ r  |              w xY wY nt>          $ r  |              w xY wt          | j        dd          r	 t          | j        dd
          pd}t          | dd
          |k    r || _         |                     d| d            t          | dd
          }|s|                     |          }||f |             S #  |             w xY w	 t          | j        dd
          }|r6t          | dd
          |k    r || _         |                     d!| d"           nlt          | j        d#d
          }t          | j        d$d
          }|r>||f}t          | d%d
          |k    r%|| _!        |                     d&| d'|pd d(           | j"        #                                }|r|$                    d)|d*           tK          ||           | &                                 |                     |          }|| _'        | j(        r	 | )                    |           |r7| j(        *                    | j        |           tW                      | _,        d}	n	 | -                    |           n# t           $ r Y nw xY w| j(        .                    | j                  }| j(        /                    | j        d+           | j        }ta          j1                    2                    d,           d-tg          j4                    j5        d
d.          | _        	 d/d0l6m7}  || j                   n$# t           $ r | j        tp          j9        d1<   Y nw xY w	 d/d2l:m;}  || j                   n# t           $ r Y nw xY wd| _<        	 | j(        =                    | j        | j>        ptp          j9        ?                    d3d4          | j        | j@        |5           n# t           $ r}t
                              d6||           || _        	 d/d0l6m7}  || j                   n$# t           $ r | j        tp          j9        d1<   Y nw xY w	 d/d2l:m;}  || j                   n# t           $ r Y nw xY w	 | j(        A                    |           n# t           $ r Y nw xY wd
}d| _<         d
}~ww xY wd| _<        	 d/d7lBmC}  ||| j        d+8           n2# t           $ r%}t
          D                    d9|           Y d
}~nd
}~ww xY w|ru	 | j(        E                    |          } | j(        F                    | j        |            n9# t          t           f$ r%}!t
          D                    d:|!           Y d
}!~!nd
}!~!ww xY w| j(        G                    | j        |           d/| _H        ny# t           $ rl}!t                      ?                    d;          &|s$t
                              d<| j        pd=|!           nt
                              d>|!           Y d
}!~!nd
}!~!ww xY wt                      ?                    d;          }"t	          |"          p|}#|"p| j        pd}$	 |#r[t          | j        d?          rF| j        K                    | j        pdd+|$t          | d@d
          pd4t          | dAd
          B           n2# t           $ r%}%t
          D                    dC|%           Y d
}%~%nd
}%~%ww xY w	 |#r,| j        r%| j        L                    | j        pd|$dd+D           n2# t           $ r%}&t
          D                    dE|&           Y d
}&~&nd
}&~&ww xY w| j        jM        }'|'dFk    r)| jN         dG|' dH}(|(| _O        | 	                    |(           t          | dId
          rh	 | P                    dJ| j>        pd| j        |"pd|| j        jM        dK           n2# t           $ r%}!t
          D                    dL|!           Y d
}!~!nd
}!~!ww xY w|	| _Q        t          ||pd| jS        pd
M          })|)| j        _T        dN| j        _U        d/| j        _V        d| j        _W        	 d/dOlXmY}*  |*|           n# t           $ r Y nw xY wt
                              dP| j        pd|t          |          |)d           ||f |             S #  |             w xY w)Ru  Compress conversation context and split the session in SQLite.

    Args:
        agent: The owning :class:`AIAgent`.
        messages: Current message history (will be summarised).
        system_message: Current system prompt; rebuilt after compression.
        approx_tokens: Pre-compression token estimate, logged for ops.
        task_id: Tool task scope (used for clearing file-read dedup state).
        focus_topic: Optional focus string for guided compression — the
            summariser will prioritise preserving information related to
            this topic.  Inspired by Claude Code's ``/compact <focus>``.
        force: If True, bypass any active summary-failure cooldown.  Set
            by the manual ``/compress`` slash command so users can retry
            immediately after an auto-compress abort.  Auto-compress
            callers use the default ``False``.

    Returns:
        ``(compressed_messages, new_system_prompt)`` tuple.  When
        compression aborts (aux LLM failed to produce a usable summary),
        returns the original messages unchanged and the existing system
        prompt — the session is NOT rotated.  Callers should detect the
        no-op via ``len(returned) == len(input)`` and stop the retry loop.
     _compression_feasibility_checkedFTcompression_in_placezPcontext compression started: session=%s messages=%d tokens=~%s model=%s focus=%rnoneri   unknown_session_dbNr]   _compression_lock_ttl_secondsg     r@"_compression_lock_refresh_intervalrH    _last_compression_lock_error_sidu   compression lock subsystem unavailable for session=%s (%s: %s) — proceeding without lock. This usually means a stale in-memory module after an update; restart the process (or `hermes update`) to resync.u~   compression skipped: another path is compressing session=%s (holder=%s) — returning messages unchanged to avoid session fork"_last_compression_lock_warning_siduw   ⚠ Skipping concurrent compression — another path is already compressing this session. Will retry after it finishes._cached_system_promptr   r$   c                                                       PrPrP	                                dS # t          $ r&} t                              d|            Y d} ~ dS d} ~ ww xY wdS dS dS )z?Release the lock keyed on the OLD session_id (before rotation).Nz#compression lock release failed: %s)rF   release_compression_lockrK   rL   rM   )_rel_err_lock_db_lock_holder_lock_refresher	_lock_sids    r   _release_lockz'compress_context.<locals>._release_lock^  s    &  """I,N11)\JJJJJ N N NBHMMMMMMMMMN  s   7 
A'A""A')current_tokensr   r   )r   _last_compress_aborted_last_summary_errorzunknown error!_last_compression_summary_warningu   ⚠ Compression aborted: uz   . No messages were dropped — conversation continues unchanged. Run /compress to retry, or /new to start a fresh session.u    ⚠ Compression summary failed: z%. Inserted a fallback context marker._last_aux_model_failure_model_last_aux_model_failure_error_last_aux_fallback_warning_keyu"   ℹ Configured compression model 'z
' failed (uS   ). Recovered using main model — check auxiliary.compression.model in config.yaml.r   r   r\   z%Y%m%d_%H%M%Sr      r   )set_current_session_idHERMES_SESSION_ID)set_session_contextHERMES_SESSION_SOURCEcli)r   sourcerl   model_configparent_session_iduf   Compression child session create failed (%s) — rolling back to parent session %s to avoid an orphan.)migrate_goal_to_session)reasonz)Could not migrate goal on compression: %sz,Could not propagate title on compression: %sold_session_idzKCompression rotation aborted and rolled back to the parent session (%s): %srm   uK   Session DB compression split failed — new session will NOT be indexed: %son_session_startplatform_gateway_session_key)boundary_reasonr   r   conversation_idz1context engine on_session_start (compression): %s)r   resetr   z2memory manager on_session_switch (compression): %s   u   ⚠️  Session compressed u>    times — accuracy may degrade. Consider /new to start fresh.event_callbackzsession:compress)r   r   r   in_placecompression_countz,event_callback error on session:compress: %s)system_prompttoolsra   )reset_file_dedupz^context compression done: session=%s messages=%d->%d rough_tokens=~%s awaiting_real_usage=truerQ   )Zry   r   r   lenr   rL   infor   rl   rw   COMPACTION_STATUSr!   	TypeErrorr}   r   try_acquire_compression_lockrK   r   rx   typerR   get_compression_lock_holderr   _emit_warning_build_system_promptr   r>   _memory_manageron_pre_compressr~   compressBaseExceptionr   r   _todo_storeformat_for_injectionr   r   _invalidate_system_promptr   r   commit_memory_sessionarchive_and_compactrB   _flushed_db_message_ids_flush_messages_to_session_dbget_session_titleend_sessionr   nowstrftimer   r   r   gateway.session_contextr   r   environhermes_loggingr   _session_db_createdcreate_sessionr   r   _session_init_model_configreopen_sessionhermes_cli.goalsr   rM   get_next_title_in_lineageset_session_titleupdate_system_prompt_last_flushed_db_idxlocalshasattrr   on_session_switchr   
log_prefixrv   r   r   r	   r   last_compression_rough_tokenslast_prompt_tokenslast_completion_tokens%awaiting_real_usage_after_compressiontools.file_toolsr   )/r
   r   r   r   r   r   r   _pre_msg_countr   compacted_in_place	_lock_ttl_lock_refresh_interval_lock_acquired	_lock_errexisting_existing_spr   r   _errsummary_error_aux_fail_model_aux_fail_err_aux_keytodo_snapshotnew_system_prompt	old_titler   r   r   _cs_errr   	_goal_err	new_titlee_old_sid_is_boundary_boundary_parent_ce_err_me_err_cc_cc_msg_compressed_estr   r   r   r   r   s/                                              @@@@r   compress_contextr=    s+   P 5<eDD 6 	,E22215.]]N GE#95AABBH 
KKZ"FN -<=9ek	   
()))0 umT22H &BI"&L"'%)H%PPYTYZZ		z"   			$U,PRVWW@DO	/66	"%BB<Y C  NN  	" 	" 	"  Lu@$GG9TT9B6> tI7   "NNNNNN	"  	* #??	JJ       NNU8  
  LuBDIIYVV;D8''-   
 !   D"5*A4HHL J$99.II\))#<&  egg N N N N N N N N N  	!11(;;;; 	 	 	D	-66xP]kv  D6  E  E

   	1::8Ta:bbJJ 	 	 	MOOO	 J     		 u')A5II 	535JDQQdUdDuA4HHDPP:>7##P P P P  
 #5*A4HHL J$99.II\)MOOOOMMOOOOv 8:OQUVV 	uA4HHMYY:G7##:} : : :   &e&>@_aeffO#E$<>]_cddM 	+];5"BDIIXUU;CE8''L_ L L)<_L L L   )>>@@ 	Jv-HHIII(:>>>'')))!66~FF&7# ^	u]u
 ++H555 B\& %99%:JJWWW 58EEE1 *.&&;;HEEEE$    !& 1 C CEDT U UI%11%2BMRRR%*%5N*2,..*A*A/*R*R'k'kUYU_UaUaUefhghfhUi'k'kE$KRRRRRR..u/?@@@@$ K K K:?:J
#6777KFFFFFF++E,<====$   05E--)88','7#(>#cRZ^^D[]b5c5c"'+).)I.< 9     % % % % T#^  
 ,:(OVVVVVV2253CDDDD( O O O>C>NBJ':;;;O!JJJJJJ//0@AAAA( ! ! ! D!!!-<<^LLLL( ! ! ! D!)- 591K%L 15E-
]LLLLLL//@PYfggggg$ ] ] ]%PR[\\\\\\\\] ! \\(-(9(S(ST](^(^I!-??@PR[\\\\ *I6 \ \ \"LL)WYZ[[[[[[[[\ !66u7GIZ[[[-.** u u u 88<< 0119(9NN2383C3JsA   
 NN#prstttu$ 88<< 011H~~1#=u'7=2
	W (@BT U U (99$*$1#3$UJ==F$+E3I4$P$P :     	W 	W 	WLLLgVVVVVVVV	W		X  5 %77$*&6(	 8     	X 	X 	XLLMwWWWWWWWW	X &8!88# G G G G G  *1E&w''' 5*D11 
	P	P$$%7 % 4""'"2&.n" ().)A)S: :      P P PKQOOOOOOOOP +='
 8+1r+%
 
 

 BQ >68 3:; 7IM F
	999999W%%%% 	 	 	D	 	l&J""	
 	
 	

 ,, 	s   C$ $C:9C:&D? ?
F"	AFF"(F> >GGH! !
H.-H.J0 0
J=<J=K   
L8+LL8LL8#L8A8O O!%D5n: Ab& *V  ?b&  
V
b& VBb& %X< ;b& <Yb& Yb& !Y8 7b& 8
Zb& Z
b& A[$ #b& $
^0.#^+\)(^+)]
^+	]

^+]%$^+%
]2/^+1]22^+6^^+
^^+^^++^00
b& ;_ b& 
`_?:b& ?`b& 
:a b& a;a61b& 6a;;*b& %n: &
d0A"dn: dA n: Af; :n: ;
g*g% n: %g**n: ..h n: 
i'in: iAn: 4k n: 
l k;6n: ;l  An: m( 'n: (
m52n: 4m55:n: :oi@  )max_dimensionapi_messagesr>  r2   c               T   | sdS 	 ddl m n3# t          $ r&}t                              d|           Y d}~dS d}~ww xY wdd}d}d dd!fd}d"d}d#d}| D ]}}t          |t                    s|                    d          }	t          |	t                    sD|	D ]5}
t          |
t                    s|
                    d          }|dk    ra|
                    d          } ||          } ||pd          \  }}|r't          |t                    r |||           |dz  }n|r|dz  }|dvr|
                    d          }t          |t                    r9|                    dd          } ||          \  }}|r||d<   |dz  }|r|dz  }t          |t                    r# ||          \  }}|r||
d<   |dz  }.|r|dz  }7|rt          
                    d|dz             |r!t                              d|dz             dS |dk    S )$u  Re-encode all native image parts at a smaller size to recover from
    image-too-large errors (Anthropic 5 MB, unknown other providers).

    Mutates ``api_messages`` in place. Returns True if any image part was
    actually replaced, False if there were no image parts to shrink or
    Pillow couldn't help (caller should surface the original error).

    Strategy: look for ``image_url`` / ``input_image`` parts carrying a
    ``data:image/...;base64,...`` payload, plus Anthropic-native
    ``{"type": "image", "source": {"type": "base64", ...}}`` blocks.
    For each one whose encoded size exceeds 4 MB (a safe target that slides
    under Anthropic's 5 MB ceiling with header overhead) or whose longest side
    exceeds ``max_dimension``, write the base64 to a tempfile, call
    ``vision_tools._resize_image_for_vision`` to produce a smaller data
    URL, and substitute it in place.

    Non-data-URL images (http/https URLs) are not touched — the provider
    fetches those itself and the size limit is different.
    Fr   )_resize_image_for_visionu6   image-shrink recovery: vision_tools unavailable — %sNi  @ data_urlr   r   Optional[tuple]c                ^   	 ddl }ddl}|                     d          \  }}}|r|                     d          sdS ddlm} |                    |                    |                    |                              5 }|j	        cddd           S # 1 swxY w Y   dS # t          $ r Y dS w xY w)zReturn ``(width, height)`` of a base64 data URL, or None on failure.

        Soft-depends on Pillow; returns None (caller falls back to a
        bytes-only check) if Pillow is missing or the payload is corrupt.
        r   Nri   data:)Image)base64io	partition
startswithPILrF  openBytesIO	b64decodesizerK   )rB  _b64_dim_io_dimheader_dr   data_d	_PILImage_imgs           r   _decode_pixelsz:try_shrink_image_parts_in_messages.<locals>._decode_pixels   s"   
	%%%%    "*"4"4S"9"9Ha !4!4W!=!= t......0B0B60J0J K KLL !PTy! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! 	 	 	44	s;   8B AB =BB BB BB 
B,+B,urltuplec                   t          | t                    r|                     d          sdS t          |           k    }|rdnd}|s( |           }|dS t	          |          k    rdS d}d}	 |                     d          \  }}}d}|                    d          rZ|t          d          d                             d	d
          d                                         }|                    d          r|}ddl}	|		                    |          }
ddddddd
                    |d          }t          j        d|d          }	 |                    |
           |                                  t          |j                  |          }	 t          |j                                      d           nN# t$          $ r Y nBw xY w# 	 t          |j                                      d           w # t$          $ r Y w w xY wxY w|sdS |dk    rHt          |          t          |           k    rdS  |          }|t	          |          k    rdS |dfS  |          }|t	          |          k    r|dfS dS t          |          t          |           k    rdS |dfS # t$          $ r+}t&                              d|           d|dufcY d}~S d}~ww xY w)u  Return ``(resized_url, unshrinkable)`` for a data URL.

        ``resized_url`` is a smaller/dimension-correct data URL, or None when
        no rewrite was applied.  ``unshrinkable`` is True only when the image
        exceeded a constraint (byte-size or dimensions) and the resize failed
        to satisfy *that same* constraint — so the caller knows retrying is
        pointless even if a different image in the request shrank.
        rE  )NFbytesNT	dimensionri   
image/jpeg;r'   r   image/z.pngz.gifz.webpz.jpgz.bmp)z	image/pngz	image/gifz
image/webpr\  z	image/jpgz	image/bmphermes_shrink_F)prefixsuffixdelete)	mime_typemax_base64_bytesr>  )
missing_ok)NTu.   image-shrink recovery: re-encode failed — %s)r{   r   rJ  r   r/   rI  splitstriprG  rN  r   tempfileNamedTemporaryFilewritecloser   r)   unlinkrK   rL   rx   )rW  needs_shrinktriggered_bydimsheaderr   datamime	mime_part_b64rawra  tmpresizednew_dimsrP   rV  rA  r>  target_bytess                   r   _shrink_data_urlz<try_shrink_image_parts_in_messages.<locals>._shrink_data_url  s    #s## 	3>>'+B+B 	; 3xx,.".8wwD 	' ">#&&D|"{4yyM))"{L&LD	2!mmC00OFAtD  )) %"3w<<==177Q??BHHJJ	''11 %$D!!!!..&&C#&$6  c$  -'u  C		#		22NN"%1"/	  NN))T)::::    DNN))T)::::    D "!zw&&w<<3s88++%: *>'22'CMMM,I,I%:~% &~g..H#x==M11"E>)!z 7||s3xx''!zE>! 	2 	2 	2NNKSQQQT11111111	2s   2CJ* 
A
G (F> =J* >
GJ* 
GJ* H	(G98H	9
HH	HH		J* &J* 8 J* J* #J*  J* &J* *
K4 KKKr   r   r   c                h   t          | t                    r|                     d          dk    rd S |                     d          }t          |t                    r|sd S t          |                     d          pd                                          }|                    d          sd}d| d| S )	Nr   rG  rq  
media_typer\  r^  rE  z;base64,)r{   r   r   r   rg  rJ  )r   rq  r|  s      r   _source_to_data_urlz?try_shrink_image_parts_in_messages.<locals>._source_to_data_url{  s    &$'' 	6::f+=+=+I+I4zz&!!$$$ 	D 	4L11A\BBHHJJ
$$X.. 	&%J1z114111r   r   r$   c                8   |                     d          \  }}}d}|                    d          rZ|t          d          d                              dd          d                                         }|                    d          r|}d| d	<   || d
<   || d<   d S )Nri   r\  rE  r]  r'   r   r^  rG  r   r|  rq  )rI  rJ  r   rf  rg  )r   rB  rp  r   rq  r|  	candidates          r   _write_data_url_to_sourcezEtry_shrink_image_parts_in_messages.<locals>._write_data_url_to_source  s    ",,S114!
W%% 	's7||}}-33C;;A>DDFFI##H-- '&
!v)|vr   r   r   imager]   r'   >   	image_urlinput_imager  zGimage-shrink recovery: re-encoded %d image part(s) to fit under %.0f MBi   u   image-shrink recovery: %d oversized image part(s) could not be shrunk under %.0f MB — not retrying (would re-send rejected payload))rB  r   r   rC  )rW  r   r   rX  )r   r   r   r   )r   r   rB  r   r   r$   )tools.vision_toolsrA  rK   rL   rx   r{   r   r   r   r   r   )r?  r>  rP   changed_countunshrinkable_oversizedrz  r}  r  r   r   partptyper   rW  rw  unshrinkableimage_valuerV  rA  ry  s    `               @@@r   "try_shrink_image_parts_in_messagesr    sm   0  u???????   OQTUUUuuuuu #L
 M    $g2 g2 g2 g2 g2 g2 g2 g2 g2R	2 	2 	2 	2	 	 	 	  '0 '0#t$$ 	'')$$'4(( 	 !	0 !	0DdD)) HHV$$E(++))&11(8(8(C(C% 0z&$77 0--fg>>>!Q&MM! 0*a/*888((;//K +t,, 0!ooeR00(8(8(=(=% 0)0K&!Q&MM! 0*a/*K-- 0(8(8(E(E% 0(/D%!Q&MM! 0*a/*C!	0F  
U<;7	
 	
 	
  

 	U"LK$@	
 	
 	

 u1s    
A<A)r   COMPACTION_STATUS_MARKERr   r   r=  r  )r
   r   r   r   )r
   r   r   r$   )r
   r   r   r   r   r   )r   r   r   r   r   r$   )r
   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   )r?  r   r>  r2   r   r   )__doc__
__future__r   loggingr   rh  r   r   r   pathlibr   typingr   r   r   rr   r	   	getLoggerrR   rL   r  r   r   r   r   r   r   r   r=  r  __all__rU   r   r   <module>r     s   8 # " " " " "  				                   ' ' ' ' ' ' ' ' ' ' > > > > > >		8	$	$ 0 c'ccc 
   (L L L L L L L L^D
 D
 D
 D
N   $   .& & & &\ $(!%X X X X X X| | | | | | |~  r   