Files
openai--openai-agents-python/tests/test_run_internal_approvals.py
T
2026-07-13 12:39:17 +08:00

124 lines
3.8 KiB
Python

from __future__ import annotations
from dataclasses import dataclass
from typing import Any, cast
from openai.types.responses import ResponseFunctionToolCall
from agents import Agent
from agents.items import MessageOutputItem, ToolCallOutputItem, TResponseInputItem
from agents.run_internal.approvals import (
_build_function_tool_call_for_approval_error,
append_approval_error_output,
append_input_items_excluding_approvals,
approvals_from_step,
filter_tool_approvals,
)
from tests.utils.factories import make_message_output, make_tool_approval_item, make_tool_call
@dataclass
class _Step:
interruptions: list[Any]
@dataclass
class _NoInterruptionsStep:
value: str
class _NamespacedToolCall:
namespace = "object_namespace"
def test_filter_tool_approvals_keeps_only_approval_items() -> None:
agent = Agent(name="test")
approval = make_tool_approval_item(agent)
assert filter_tool_approvals(["text", approval, object()]) == [approval]
def test_approvals_from_step_handles_missing_and_mixed_interruptions() -> None:
agent = Agent(name="test")
approval = make_tool_approval_item(agent)
assert approvals_from_step(_NoInterruptionsStep("none")) == []
assert approvals_from_step(_Step(["other", approval])) == [approval]
def test_append_input_items_excluding_approvals_skips_approval_placeholders() -> None:
agent = Agent(name="test")
base_input: list[TResponseInputItem] = []
message = MessageOutputItem(agent=agent, raw_item=make_message_output(text="done"))
approval = make_tool_approval_item(agent, call_id="call_approval")
append_input_items_excluding_approvals(base_input, [message, approval])
assert len(base_input) == 1
assert cast(dict[str, Any], base_input[0])["type"] == "message"
def test_append_approval_error_output_emits_function_tool_output() -> None:
agent = Agent(name="test")
generated_items: list[Any] = []
append_approval_error_output(
generated_items=generated_items,
agent=agent,
tool_call={"namespace": "dict_namespace"},
tool_name="needs_approval",
call_id=None,
message="approval denied",
)
assert len(generated_items) == 1
output_item = generated_items[0]
assert isinstance(output_item, ToolCallOutputItem)
assert output_item.agent is agent
assert output_item.output == "approval denied"
assert output_item.raw_item == {
"type": "function_call_output",
"call_id": "unknown",
"output": "approval denied",
}
def test_build_function_tool_call_for_approval_error_reuses_typed_calls() -> None:
tool_call = make_tool_call(call_id="call_1", name="typed_tool")
assert (
_build_function_tool_call_for_approval_error(tool_call, "ignored", "ignored") is tool_call
)
def test_build_function_tool_call_for_approval_error_preserves_namespace_sources() -> None:
from_dict = _build_function_tool_call_for_approval_error(
{"namespace": "dict_namespace"},
"dict_tool",
"call_dict",
)
from_object = _build_function_tool_call_for_approval_error(
_NamespacedToolCall(),
"object_tool",
"call_object",
)
assert isinstance(from_dict, ResponseFunctionToolCall)
assert from_dict.namespace == "dict_namespace"
assert from_dict.call_id == "call_dict"
assert from_object.namespace == "object_namespace"
assert from_object.call_id == "call_object"
def test_build_function_tool_call_for_approval_error_ignores_empty_namespaces() -> None:
tool_call = _build_function_tool_call_for_approval_error(
{"namespace": ""},
"tool",
"call_1",
)
assert not hasattr(tool_call, "namespace") or tool_call.namespace is None
assert tool_call.name == "tool"
assert tool_call.arguments == "{}"
assert tool_call.status == "completed"