# SPDX-License-Identifier: AGPL-3.0-only # Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0 """Integration tests for the confirmation gate inside the real tool loop. These drive ``run_safetensors_tool_loop`` (no model -- hand-crafted fake generators) with ``confirm_tool_calls=True`` and resolve each pending decision inline. The slot is registered before ``tool_start`` is yielded, so resolving right after receiving that event always lands before the loop blocks. Covers: allow executes once, deny skips execution and feeds back the rejection, disabled/duplicate calls are not prompted, and a denied call does not pollute duplicate detection. """ import pytest from core.inference.safetensors_agentic import run_safetensors_tool_loop from state import tool_approvals from state.tool_approvals import TOOL_REJECTED_MESSAGE, resolve_tool_decision _SESSION = "loop-session" @pytest.fixture(autouse = True) def _clear_pending(): with tool_approvals._lock: tool_approvals._pending.clear() yield with tool_approvals._lock: tool_approvals._pending.clear() class _FakeExecuteTool: def __init__(self): self.calls = [] def __call__( self, name, arguments, *, cancel_event = None, timeout = None, session_id = None, rag_scope = None, disable_sandbox = False, ): self.calls.append((name, arguments)) return f"RESULT[{name}]" def _tool_call(name, args_json): return f'{{"name": "{name}", "arguments": {args_json}}}' def _multi_turn(turns): """A single_turn generator that yields one full snapshot per turn.""" turn_iter = iter(turns) def _gen(_messages): try: yield next(turn_iter) except StopIteration: return return _gen _DEFAULT_TOOLS = [ {"type": "function", "function": {"name": "python"}}, {"type": "function", "function": {"name": "web_search"}}, ] def _drive( turns, decisions, *, tools = None, ): """Run the loop, resolving each gated tool_start with the next decision. The advertised ``tools`` list drives the loop's enabled-tool filter (pass a list omitting a tool to make a call to it "disabled"). Returns (events, execute_calls). """ decision_iter = iter(decisions) exec_fn = _FakeExecuteTool() gen = run_safetensors_tool_loop( single_turn = _multi_turn(turns), messages = [{"role": "user", "content": "hi"}], tools = _DEFAULT_TOOLS if tools is None else tools, execute_tool = exec_fn, session_id = _SESSION, confirm_tool_calls = True, ) events = [] for ev in gen: events.append(ev) if ev["type"] == "tool_start" and ev.get("awaiting_confirmation"): # Slot is already registered (begin ran before this yield), so # the decision lands before the loop enters its blocking wait. resolve_tool_decision(ev["approval_id"], next(decision_iter), session_id = _SESSION) return events, exec_fn.calls def _tool_starts(events): return [e for e in events if e["type"] == "tool_start"] def _tool_ends(events): return [e for e in events if e["type"] == "tool_end"] def test_allow_executes_the_tool_once(): events, calls = _drive( [_tool_call("python", '{"code": "print(1)"}'), "final answer"], ["allow"], ) starts = _tool_starts(events) assert len(starts) == 1 assert starts[0]["awaiting_confirmation"] is True assert starts[0]["approval_id"] assert calls == [("python", {"code": "print(1)"})] assert _tool_ends(events)[0]["result"] == "RESULT[python]" def test_deny_skips_execution_and_feeds_rejection(): events, calls = _drive( [_tool_call("python", '{"code": "print(1)"}'), "final answer"], ["deny"], ) assert calls == [] # tool never ran assert _tool_ends(events)[0]["result"] == TOOL_REJECTED_MESSAGE def test_disabled_tool_is_not_prompted(): events, calls = _drive( [_tool_call("python", '{"code": "print(1)"}'), "final answer"], [], tools = [{"type": "function", "function": {"name": "web_search"}}], ) assert _tool_starts(events) == [] assert _tool_ends(events) == [] assert calls == [] def test_duplicate_call_is_not_prompted(): same = _tool_call("python", '{"code": "print(1)"}') events, calls = _drive([same, same, "final answer"], ["allow"]) starts = _tool_starts(events) assert len(starts) == 1 assert starts[0]["awaiting_confirmation"] is True assert calls == [("python", {"code": "print(1)"})] assert len(_tool_ends(events)) == 1 def test_denied_call_can_be_reissued_and_approved(): # Deny, then the model re-issues the identical call -> approving it must # execute, not get suppressed as a duplicate (denied calls are not added # to the duplicate-detection history). same = _tool_call("python", '{"code": "print(1)"}') events, calls = _drive([same, same, "final answer"], ["deny", "allow"]) starts = _tool_starts(events) assert len(starts) == 2 assert starts[0]["awaiting_confirmation"] is True assert starts[1]["awaiting_confirmation"] is True # not treated as dup assert calls == [("python", {"code": "print(1)"})] # ran once, on approve ends = _tool_ends(events) assert ends[0]["result"] == TOOL_REJECTED_MESSAGE assert ends[1]["result"] == "RESULT[python]"