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 <yassin@berri.ai>
This commit is contained in:
Cursor Agent 2026-05-25 21:09:10 +00:00
parent 335afa6251
commit 9388ea9067
No known key found for this signature in database
2 changed files with 28 additions and 5 deletions

View file

@ -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, []

View file

@ -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