fix(type_discipline): satisfy type discipline gate and ruff formatting

This commit is contained in:
Rahul Kumar 2026-10-24 11:11:26 +05:30
parent 065df9d7fb
commit 800b123a1b
3 changed files with 101 additions and 80 deletions

View file

@ -662,7 +662,9 @@ class AnthropicMessagesHandler(BaseTranslation):
)
if not scan_only_tool_results:
retained_names: Final = frozenset(
name for t in (data.get("tools") or []) for name in anthropic_tool_names(t)
name
for t in (data.get("tools") or ())
for name in anthropic_tool_names(t) # comprehension-ok: flattens tool names across tools
)
self._strip_disallowed_tool_additions(data, retained_names)
@ -940,55 +942,71 @@ class AnthropicMessagesHandler(BaseTranslation):
"""Extract every tool name in an Anthropic messages request: tools[].name, plus
tools[].function.name for OpenAI-format tools the bridge forwards verbatim,
as well as mid-conversation tool additions in messages."""
names = [name for tool in list_field(data, "tools") for name in anthropic_tool_names(tool)]
messages = data.get("messages")
if isinstance(messages, Sequence):
for m in messages:
if isinstance(m, Mapping):
content = m.get("content")
if isinstance(content, Sequence) and not isinstance(content, (str, bytes)):
for block in content:
if isinstance(block, Mapping) and block.get("type") == "tool_addition":
tool = extract_tool_from_addition_block(block)
if tool is not None:
for name in anthropic_tool_names(tool):
if name not in names:
names.append(name)
return names
top_names: Final = [ # comprehension-ok: flattens tool names across tools
name for tool in list_field(data, "tools") for name in anthropic_tool_names(tool)
]
messages: Final = data.get("messages")
if not isinstance(messages, Sequence):
return top_names
extra_names: Final[list[str]] = [] # mutable-ok: accumulator
for m in messages:
if isinstance(m, Mapping):
content: Final = m.get("content")
if isinstance(content, Sequence) and not isinstance(content, (str, bytes)):
for block in content:
if isinstance(block, Mapping) and block.get("type") == "tool_addition":
tool: Final = extract_tool_from_addition_block(block)
if tool is not None:
for name in anthropic_tool_names(tool):
if name not in top_names and name not in extra_names:
extra_names.append(name)
return [*top_names, *extra_names]
@classmethod
def _strip_disallowed_tool_additions(cls, data: dict, retained_names: frozenset[str]) -> None:
messages = data.get("messages")
def _strip_disallowed_tool_additions(cls, data: Mapping[str, object], retained_names: frozenset[str]) -> None:
messages: Final = data.get("messages")
if not isinstance(messages, list):
return
cleaned_messages: list[object] = []
cleaned_messages: Final[list[object]] = [] # mutable-ok: accumulator
for m in messages:
if not isinstance(m, dict):
cleaned_messages.append(m)
continue
content = m.get("content")
content: Final = m.get("content")
if not isinstance(content, list):
cleaned_messages.append(m)
continue
had_disallowed_addition = False
remaining_blocks: list[object] = []
for block in content:
if isinstance(block, dict) and block.get("type") == "tool_addition":
tool = extract_tool_from_addition_block(block)
tool_names = anthropic_tool_names(tool) if tool else ()
if tool_names and all(name in retained_names for name in tool_names):
remaining_blocks.append(block)
else:
had_disallowed_addition = True
else:
remaining_blocks.append(block)
had_disallowed_addition: Final = any(
isinstance(block, dict)
and block.get("type") == "tool_addition"
and (
not (tool := extract_tool_from_addition_block(block))
or not (tool_names := anthropic_tool_names(tool))
or not all(name in retained_names for name in tool_names)
)
for block in content
)
if not had_disallowed_addition:
cleaned_messages.append(m)
continue
remaining_blocks: Final[list[object]] = [ # mutable-ok: accumulator
block
for block in content
if not (
isinstance(block, dict)
and block.get("type") == "tool_addition"
and (
not (tool := extract_tool_from_addition_block(block))
or not (tool_names := anthropic_tool_names(tool))
or not all(name in retained_names for name in tool_names)
)
)
]
if remaining_blocks:
new_m = dict(m)
new_m: Final[dict[str, object]] = dict(m) # mutable-ok: message clone
new_m["content"] = remaining_blocks
cleaned_messages.append(new_m)
elif not had_disallowed_addition:
cleaned_messages.append(m)
data["messages"] = cleaned_messages
data["messages"] = cleaned_messages # rebind-ok: update request in place
@classmethod
def _extract_input_text_and_images(

View file

@ -1236,73 +1236,78 @@ class LiteLLMAnthropicMessagesAdapter:
self,
messages: Sequence[Mapping[str, object] | AllAnthropicPassThroughMessageValues],
initial_tools: Sequence[AllAnthropicToolsValues] | None = None,
) -> tuple[list[AllAnthropicToolsValues] | None, list[AllAnthropicPassThroughMessageValues]]:
) -> tuple[tuple[AllAnthropicToolsValues, ...] | None, tuple[AllAnthropicPassThroughMessageValues, ...]]:
"""
Extract and apply mid-conversation tool changes (tool_addition, tool_removal)
such as those sent by Claude Code under mid-conversation-tool-changes beta.
Returns:
Tuple of (active_tools, cleaned_messages)
- active_tools: Updated tools list with additions applied and removals removed,
- active_tools: Updated tools tuple with additions applied and removals removed,
or None if no tools exist and none were added.
- cleaned_messages: Messages list with tool_addition and tool_removal blocks stripped,
- cleaned_messages: Messages tuple with tool_addition and tool_removal blocks stripped,
and carrier messages left without content dropped.
"""
active_tools: list[AllAnthropicToolsValues] = list(initial_tools) if initial_tools else []
had_tool_changes = False
cleaned_messages: list[AllAnthropicPassThroughMessageValues] = []
active_tools: tuple[AllAnthropicToolsValues, ...] = tuple(initial_tools or ()) # rebind-ok: tool updates
had_tool_changes: bool = False # rebind-ok: state flag
cleaned_messages: Final[list[AllAnthropicPassThroughMessageValues]] = [] # mutable-ok: accumulator
for m in messages:
if not isinstance(m, dict):
cleaned_messages.append(m) # type: ignore[arg-type]
if not isinstance(m, Mapping):
cleaned_messages.append(m) # pyright: ignore[reportArgumentType] # non-mapping passthrough
continue
content = m.get("content")
if not isinstance(content, list):
cleaned_messages.append(cast(AllAnthropicPassThroughMessageValues, m))
content: Final = m.get("content")
if not isinstance(content, Sequence) or isinstance(content, (str, bytes)):
msg_val: Final = cast(AllAnthropicPassThroughMessageValues, m) # cast-ok: msg
cleaned_messages.append(msg_val)
continue
remaining_blocks: list[dict[str, Any]] = []
remaining_blocks: Final[list[object]] = [] # mutable-ok: accumulator
for block in content:
if not isinstance(block, dict):
if not isinstance(block, Mapping):
remaining_blocks.append(block)
continue
btype = block.get("type")
btype: Final = block.get("type")
if btype == "tool_addition":
had_tool_changes = True
raw_tool = extract_tool_from_addition_block(block)
if isinstance(raw_tool, dict) and raw_tool.get("name"):
tool_name = str(raw_tool["name"])
if not any(isinstance(t, dict) and t.get("name") == tool_name for t in active_tools):
active_tools.append(cast(AllAnthropicToolsValues, raw_tool))
raw_tool: Final = extract_tool_from_addition_block(block)
if isinstance(raw_tool, Mapping) and raw_tool.get("name"):
tool_name: Final = str(raw_tool["name"])
if not any(isinstance(t, Mapping) and t.get("name") == tool_name for t in active_tools):
added_tool: Final = cast(AllAnthropicToolsValues, dict(raw_tool)) # cast-ok: tool
active_tools = (*active_tools, added_tool)
elif btype == "tool_removal":
had_tool_changes = True
rem_name = (
rem_name: Final = (
block.get("tool_name")
or block.get("name")
or (block.get("tool", {}).get("name") if isinstance(block.get("tool"), dict) else None)
or (block.get("tool", {}).get("name") if isinstance(block.get("tool"), Mapping) else None)
or (block.get("tool") if isinstance(block.get("tool"), str) else None)
)
if rem_name:
rem_name_str = str(rem_name)
active_tools = [
t for t in active_tools if not (isinstance(t, dict) and t.get("name") == rem_name_str)
]
rem_name_str: Final = str(rem_name)
active_tools = tuple(
t for t in active_tools if not (isinstance(t, Mapping) and t.get("name") == rem_name_str)
)
else:
remaining_blocks.append(block)
if remaining_blocks:
new_m = dict(m)
new_m: Final[dict[str, object]] = dict(m) # mutable-ok: message clone
new_m["content"] = remaining_blocks
cleaned_messages.append(cast(AllAnthropicPassThroughMessageValues, new_m))
new_msg: Final = cast(AllAnthropicPassThroughMessageValues, new_m) # cast-ok: msg
cleaned_messages.append(new_msg)
elif not had_tool_changes:
cleaned_messages.append(cast(AllAnthropicPassThroughMessageValues, m))
clean_msg: Final = cast(AllAnthropicPassThroughMessageValues, m) # cast-ok: msg
cleaned_messages.append(clean_msg)
if not had_tool_changes and initial_tools is None:
return None, cleaned_messages
return None, tuple(cleaned_messages)
return active_tools if active_tools else (
[] if initial_tools is not None or had_tool_changes else None
), cleaned_messages
return (
active_tools if active_tools else (() if initial_tools is not None or had_tool_changes else None),
tuple(cleaned_messages),
)
def translate_anthropic_to_openai(
self,
@ -1320,8 +1325,6 @@ class LiteLLMAnthropicMessagesAdapter:
- tool_name_mapping maps truncated tool names back to original names
for tools that exceeded OpenAI's 64-char limit
"""
# Debug: Processing Anthropic message request
new_messages: list[AllMessageValues] = []
tool_name_mapping: dict[str, str] = {}
## CONVERT ANTHROPIC MESSAGES TO OPENAI
@ -1336,17 +1339,17 @@ class LiteLLMAnthropicMessagesAdapter:
messages=messages_list,
initial_tools=anthropic_message_request.get("tools"),
)
request_for_translation: Final[AnthropicMessagesRequest] = cast(
request_for_translation: Final[AnthropicMessagesRequest] = cast( # cast-ok: anthropic request type
AnthropicMessagesRequest, dict(anthropic_message_request)
)
if active_tools is not None:
if active_tools:
request_for_translation["tools"] = active_tools
request_for_translation["tools"] = list(active_tools)
else:
request_for_translation.pop("tools", None)
new_messages = self.translate_anthropic_messages_to_openai(
messages=cleaned_messages,
new_messages: Final = self.translate_anthropic_messages_to_openai(
messages=list(cleaned_messages),
model=request_for_translation.get("model"),
custom_llm_provider=custom_llm_provider,
preserve_midturn_system=preserve_midturn_system,

View file

@ -319,22 +319,22 @@ def anthropic_tool_name(tool: object) -> str | None:
return names[0] if names else None
def extract_tool_from_addition_block(block: object) -> dict[str, object] | None:
def extract_tool_from_addition_block(block: object) -> Mapping[str, object] | None:
"""Extract raw tool dict from an Anthropic tool_addition block."""
if not isinstance(block, Mapping):
return None
tool_data = block.get("tool")
if isinstance(tool_data, dict):
tool_data: Final = block.get("tool")
if isinstance(tool_data, Mapping):
if (
tool_data.get("type") == "tool_definition"
and "definition" in tool_data
and isinstance(tool_data["definition"], dict)
and isinstance(tool_data["definition"], Mapping)
):
return tool_data["definition"]
return tool_data
if "definition" in block and isinstance(block["definition"], dict):
if "definition" in block and isinstance(block["definition"], Mapping):
return block["definition"]
if isinstance(block.get("tool_definition"), dict):
if isinstance(block.get("tool_definition"), Mapping):
return block["tool_definition"]
return None