"""Verify run_with_tool_loop supports a custom send_func for vendors that don't use send_openai_compatible (gemini_cli, gemini, anthropic, deepseek). The vendor provides a send_func that returns a NormalizedResponse, and the helper handles history + dispatch. """ from __future__ import annotations from typing import Any from unittest.mock import MagicMock, patch from src.openai_compatible import NormalizedResponse 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_uses_send_func_when_provided() -> None: client = MagicMock() def send_func(_round_idx: int) -> NormalizedResponse: return _make_normalized_response(f"from-send-func-{_round_idx}") result = run_with_tool_loop( client, request=lambda _i: MagicMock(), # should be IGNORED base_dir=".", vendor_name="custom", send_func=send_func, ) assert result == "from-send-func-0" def test_run_with_tool_loop_dispatches_via_send_func() -> None: client = MagicMock() tool_resp = _make_normalized_response( "first", tool_calls=[{"id": "c1", "type": "function", "function": {"name": "t", "arguments": "{}"}}] ) final = _make_normalized_response("done") def send_func(round_idx: int) -> NormalizedResponse: return [tool_resp, final][round_idx] with patch("src.ai_client._execute_tool_calls_concurrently", return_value=[("t", "c1", "r", "")]) as dispatch: result = run_with_tool_loop( client, request=lambda _i: MagicMock(), base_dir=".", vendor_name="custom", send_func=send_func, ) assert result == "done" assert dispatch.call_count == 1