From 571356c5b9aed975f1c9c0dfdc1c7c2c5b6d3fec Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 27 May 2026 12:28:59 +0000 Subject: [PATCH] Fix: skip client compaction pre-processing when compact_20260112 polyfill will run The _prepare_context_managed_request helper unconditionally applied apply_client_compaction_block_history before invoking the polyfill. When the request also configured a compact_20260112 spec, that pre-processing consumed the client-sent compaction block and collapsed the message history to just the latest user question, starving the polyfill of conversation context. The polyfill's own Phase A (_slice_around_compaction_block) already handles client compaction blocks correctly and inspects the full post-compaction tail for the token-threshold check, so the pre-processing is both redundant and destructive in this case. Now the pre-processing only runs when no compact_20260112 polyfill spec will execute (no spec, drop_params on, or only non-compact edits like clear_tool_uses_20250919). Co-authored-by: Yassin Kortam --- .../adapters/handler.py | 78 ++++++++++++++++--- 1 file changed, 68 insertions(+), 10 deletions(-) diff --git a/litellm/llms/anthropic/experimental_pass_through/adapters/handler.py b/litellm/llms/anthropic/experimental_pass_through/adapters/handler.py index 0280c46f864..16117509429 100644 --- a/litellm/llms/anthropic/experimental_pass_through/adapters/handler.py +++ b/litellm/llms/anthropic/experimental_pass_through/adapters/handler.py @@ -36,9 +36,7 @@ if TYPE_CHECKING: # Anthropic-only keys already mapped by the translator; strip on extra_kwargs re-merge. -ANTHROPIC_ONLY_REQUEST_KEYS: frozenset[str] = frozenset( - {"output_config"} -) +ANTHROPIC_ONLY_REQUEST_KEYS: frozenset[str] = frozenset({"output_config"}) async def _prepare_context_managed_request( @@ -57,14 +55,30 @@ async def _prepare_context_managed_request( apply_client_compaction_block_history, ) - history_result = apply_client_compaction_block_history( - messages=cast(List[Dict[str, Any]], messages), - system=system, + # Skip the client-history pre-processing when a ``compact_20260112`` + # polyfill spec will run: that editor already slices around any client-sent + # compaction block in its Phase A (and uses the full post-compaction tail + # for its token-threshold check). Pre-collapsing to just the latest user + # question here would starve the polyfill of conversation context and + # silently drop intermediate turns. + polyfill_will_run = _polyfill_will_run( + context_management_spec=context_management_spec, + drop_params=drop_params, ) - working_messages = ( - history_result.messages if history_result is not None else messages - ) - working_system = history_result.system if history_result is not None else system + + if polyfill_will_run: + history_result: Optional[PolyfillResult] = None + working_messages: List[Dict] = messages + working_system: Optional[Any] = system + else: + history_result = apply_client_compaction_block_history( + messages=cast(List[Dict[str, Any]], messages), + system=system, + ) + working_messages = ( + history_result.messages if history_result is not None else messages + ) + working_system = history_result.system if history_result is not None else system polyfill_result = await _run_polyfill_if_enabled( model=model, @@ -82,6 +96,50 @@ async def _prepare_context_managed_request( return history_result +def _polyfill_will_run( + *, + context_management_spec: Any, + drop_params: Optional[bool], +) -> bool: + """Return True when ``compact_20260112`` will run via the polyfill dispatcher. + + Mirrors the gating in ``_run_polyfill_if_enabled``: an empty spec or + effective ``drop_params`` short-circuits the polyfill. The pre-processing + skip only applies when the dispatcher will actually invoke + ``apply_compact_20260112`` (which has its own compaction-block slicing). + """ + if not context_management_spec: + return False + + effective_drop_params = ( + drop_params if drop_params is not None else litellm.drop_params + ) + if effective_drop_params: + return False + + from litellm.llms.anthropic.experimental_pass_through.context_management.constants import ( + COMPACT_EDIT_TYPE, + ) + + spec = context_management_spec + if isinstance(spec, list): + try: + from litellm.llms.anthropic.chat.transformation import AnthropicConfig + + spec = AnthropicConfig.map_openai_context_management_to_anthropic(spec) + except Exception: + return False + + edits = spec.get("edits") if isinstance(spec, dict) else None + if not isinstance(edits, list): + return False + + return any( + isinstance(edit, dict) and edit.get("type") == COMPACT_EDIT_TYPE + for edit in edits + ) + + async def _run_polyfill_if_enabled( *, model: str,