From 9388ea9067212735a73c75050b211b3367c149a4 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 25 May 2026 21:09:10 +0000 Subject: [PATCH] fix(context_management): align tool_use counts and normalize list spec - _count_tool_uses now requires a string id, matching _collect_tool_use_ids_in_order so the tool_uses trigger can't fire on blocks that aren't clearable. - apply_context_management dispatcher now accepts the OpenAI list form and normalizes it via AnthropicConfig.map_openai_context_management_to_anthropic, so the polyfill path no longer silently no-ops on list input. Co-authored-by: Yassin Kortam --- .../context_management/dispatcher.py | 23 ++++++++++++++++--- .../editors/clear_tool_uses.py | 10 ++++++-- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/litellm/llms/anthropic/experimental_pass_through/context_management/dispatcher.py b/litellm/llms/anthropic/experimental_pass_through/context_management/dispatcher.py index 17a510b8aba..93881580e92 100644 --- a/litellm/llms/anthropic/experimental_pass_through/context_management/dispatcher.py +++ b/litellm/llms/anthropic/experimental_pass_through/context_management/dispatcher.py @@ -1,6 +1,6 @@ """Dispatch ``context_management`` edits to registered polyfill editors.""" -from typing import Any, Callable, Dict, List, Optional, Tuple +from typing import Any, Callable, Dict, List, Optional, Tuple, Union from litellm._logging import verbose_logger from litellm.types.llms.anthropic import AppliedEdit @@ -21,10 +21,27 @@ def apply_context_management( messages: List[Dict[str, Any]], tools: Optional[List[Dict[str, Any]]], system: Any, - context_management_spec: Dict[str, Any], + context_management_spec: Union[Dict[str, Any], List[Dict[str, Any]], None], ) -> Tuple[List[Dict[str, Any]], List[AppliedEdit]]: """Run edits in order; return (messages, applied_edits that fired).""" - edits = context_management_spec.get("edits") if context_management_spec else None + # Accept both Anthropic-native dict form and OpenAI list form. The other + # provider paths normalize via ``map_openai_context_management_to_anthropic`` + # before dispatching; do the same here so the polyfill path doesn't silently + # no-op on list input. + if isinstance(context_management_spec, list): + from litellm.llms.anthropic.chat.transformation import AnthropicConfig + + context_management_spec = ( + AnthropicConfig.map_openai_context_management_to_anthropic( + context_management_spec + ) + ) + + edits = ( + context_management_spec.get("edits") + if isinstance(context_management_spec, dict) + else None + ) if not edits or not isinstance(edits, list): return messages, [] diff --git a/litellm/llms/anthropic/experimental_pass_through/context_management/editors/clear_tool_uses.py b/litellm/llms/anthropic/experimental_pass_through/context_management/editors/clear_tool_uses.py index 4e9bab0f4e2..a6c102db2b9 100644 --- a/litellm/llms/anthropic/experimental_pass_through/context_management/editors/clear_tool_uses.py +++ b/litellm/llms/anthropic/experimental_pass_through/context_management/editors/clear_tool_uses.py @@ -15,14 +15,20 @@ from ..placeholders import build_cleared_tool_result_content def _count_tool_uses(messages: List[Dict[str, Any]]) -> int: - """Return the number of tool_use content blocks across all messages.""" + """Return the number of tool_use content blocks across all messages. + + Only counts blocks with a string ``id`` to stay consistent with + :func:`_collect_tool_use_ids_in_order`, which is the source of truth for + which blocks are clearable. + """ count = 0 for msg in messages: content = msg.get("content") if isinstance(content, list): for block in content: if isinstance(block, dict) and block.get("type") == "tool_use": - count += 1 + if isinstance(block.get("id"), str): + count += 1 return count