"""Verify run_with_tool_loop supports a per-round request_builder callback. Vendors that mutate their history list (e.g. MiniMax) need to rebuild the messages on each round so the API sees the latest tool results. run_with_tool_loop accepts a callable as the 2nd arg to enable this. """ from __future__ import annotations from typing import Any from unittest.mock import MagicMock, patch from src.openai_compatible import NormalizedResponse, OpenAICompatibleRequest from src.ai_client import run_with_tool_loop from src.vendor_capabilities import VendorCapabilities def _make_normalized_response(text: str = "ok", tool_calls: list[dict[str, Any]] | None = None) -> NormalizedResponse: return NormalizedResponse( text=text, tool_calls=tool_calls or [], usage_input_tokens=10, usage_output_tokens=5, usage_cache_read_tokens=0, usage_cache_creation_tokens=0, raw_response=None, ) def test_run_with_tool_loop_calls_request_builder_each_round() -> None: caps = VendorCapabilities(vendor="test", model="test-model", tool_calling=True, context_window=8192) client = MagicMock() tool_response = _make_normalized_response( "first", tool_calls=[{"id": "c1", "type": "function", "function": {"name": "noop", "arguments": "{}"}}] ) final = _make_normalized_response("done") builder_calls: list[int] = [] def builder(round_idx: int) -> OpenAICompatibleRequest: builder_calls.append(round_idx) return OpenAICompatibleRequest(messages=[{"role": "user", "content": f"round={round_idx}"}], model="m") with patch("src.ai_client.send_openai_compatible", side_effect=[tool_response, final]), \ patch("src.ai_client._execute_tool_calls_concurrently", return_value=[("noop", "c1", "r", "")]): result = run_with_tool_loop( client, builder, capabilities=caps, pre_tool_callback=None, qa_callback=None, patch_callback=None, base_dir=".", vendor_name="test", history_lock=None, history=None, ) assert result == "done" assert len(builder_calls) >= 2