import json
from types import SimpleNamespace


def _tool_call(name: str, arguments):
    return SimpleNamespace(
        id="call_1",
        type="function",
        function=SimpleNamespace(name=name, arguments=arguments),
    )


def _response_with_tool_call(arguments):
    assistant = SimpleNamespace(
        content=None,
        reasoning=None,
        tool_calls=[_tool_call("read_file", arguments)],
    )
    choice = SimpleNamespace(message=assistant, finish_reason="tool_calls")
    return SimpleNamespace(choices=[choice], usage=None)


class _FakeChatCompletions:
    def __init__(self):
        self.calls = 0

    def create(self, **kwargs):
        self.calls += 1
        if self.calls == 1:
            return _response_with_tool_call({"path": "README.md"})
        return SimpleNamespace(
            choices=[
                SimpleNamespace(
                    message=SimpleNamespace(content="done", reasoning=None, tool_calls=[]),
                    finish_reason="stop",
                )
            ],
            usage=None,
        )


class _FakeClient:
    def __init__(self):
        self.chat = SimpleNamespace(completions=_FakeChatCompletions())


def test_tool_call_validation_accepts_dict_arguments(monkeypatch):
    from run_agent import AIAgent

    monkeypatch.setattr("run_agent.OpenAI", lambda **kwargs: _FakeClient())
    monkeypatch.setattr(
        "run_agent.get_tool_definitions",
        lambda *args, **kwargs: [{"function": {"name": "read_file"}}],
    )
    monkeypatch.setattr(
        "run_agent.handle_function_call",
        lambda name, args, task_id=None, **kwargs: json.dumps({"ok": True, "args": args}),
    )

    agent = AIAgent(
        model="test-model",
        api_key="test-key",
        base_url="http://localhost:8080/v1",
        platform="cli",
        max_iterations=3,
        quiet_mode=True,
        skip_memory=True,
    )
    agent._disable_streaming = True

    result = agent.run_conversation("read the file")

    # The conversation hits max_iterations=3 (3 tool turns then forced summary).
    # PR #34470 adds an explainer suffix to abnormal turn endings so users
    # understand why the response is short instead of seeing a blank reply.
    # The exact suffix wording is owned by conversation_loop; this test only
    # cares that the model's actual text ('done') survives at the start.
    assert result["final_response"].startswith("done")
