File size: 4,618 Bytes
bf7be31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
from types import SimpleNamespace

import src.agent as agent_module


class FakeToolCall:
    def __init__(self, call_id: str, name: str, arguments: str):
        self.id = call_id
        self.function = SimpleNamespace(
            name=name,
            arguments=arguments,
        )

    def model_dump(self):
        return {
            "id": self.id,
            "type": "function",
            "function": {
                "name": self.function.name,
                "arguments": self.function.arguments,
            },
        }


class FakeMessage:
    def __init__(self, content=None, tool_calls=None):
        self.content = content
        self.tool_calls = tool_calls


class FakeResponse:
    def __init__(self, message):
        self.choices = [SimpleNamespace(message=message)]


class FakeCompletions:
    def __init__(self, responses):
        self._responses = iter(responses)

    def create(self, **_kwargs):
        return next(self._responses)


class FakeClient:
    def __init__(self, responses):
        self.chat = SimpleNamespace(
            completions=FakeCompletions(responses)
        )


def _patch_client(monkeypatch, responses):
    monkeypatch.setattr(
        agent_module,
        "get_client",
        lambda: FakeClient(responses),
    )
    monkeypatch.setattr(
        agent_module,
        "write_audit_record",
        lambda call, result: None,
    )


def test_run_agent_iter_emits_real_authorization_and_execution_events(monkeypatch):
    _patch_client(
        monkeypatch,
        [
            FakeResponse(
                FakeMessage(
                    tool_calls=[
                        FakeToolCall(
                            "call-1",
                            "calculator",
                            '{"expression": "12 * 4"}',
                        )
                    ]
                )
            ),
            FakeResponse(
                FakeMessage(
                    content="The result is 48.",
                    tool_calls=None,
                )
            ),
        ],
    )

    events = list(
        agent_module.run_agent_iter("What is 12 multiplied by 4?")
    )

    event_names = [event.event for event in events]

    assert event_names == [
        "run_started",
        "model_thinking",
        "tool_requested",
        "tool_approved",
        "tool_succeeded",
        "model_thinking",
        "final_answer",
    ]

    approved = next(event for event in events if event.event == "tool_approved")
    succeeded = next(event for event in events if event.event == "tool_succeeded")
    final = events[-1]

    assert approved.authorization is not None
    assert approved.authorization.status == "approved"
    assert succeeded.result is not None
    assert succeeded.result.status == "success"
    assert succeeded.result.output["result"] == 48
    assert final.final_result is not None
    assert final.final_result.final_answer == "The result is 48."


def test_run_agent_iter_reports_blocked_call_before_execution(monkeypatch):
    _patch_client(
        monkeypatch,
        [
            FakeResponse(
                FakeMessage(
                    tool_calls=[
                        FakeToolCall(
                            "call-blocked",
                            "run_shell_command",
                            '{"command": "whoami"}',
                        )
                    ]
                )
            ),
            FakeResponse(
                FakeMessage(
                    content="That capability is not available.",
                    tool_calls=None,
                )
            ),
        ],
    )

    events = list(
        agent_module.run_agent_iter("Run a shell command.")
    )

    blocked = next(event for event in events if event.event == "tool_blocked")

    assert blocked.authorization is not None
    assert blocked.authorization.status == "blocked"
    assert blocked.result is not None
    assert blocked.result.status == "blocked"
    assert "not registered" in blocked.message
    assert not any(event.event == "tool_succeeded" for event in events)


def test_run_agent_preserves_final_result_api(monkeypatch):
    _patch_client(
        monkeypatch,
        [
            FakeResponse(
                FakeMessage(
                    content="No tool was needed.",
                    tool_calls=None,
                )
            )
        ],
    )

    result = agent_module.run_agent("Say hello without tools.")

    assert result.final_answer == "No tool was needed."
    assert result.tool_calls == []
    assert result.tool_results == []