-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Expand file tree
/
Copy pathapprovals.py
More file actions
102 lines (84 loc) · 3.29 KB
/
approvals.py
File metadata and controls
102 lines (84 loc) · 3.29 KB
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
"""
Helpers for approval handling within the run loop. Keep only execution-time utilities that
coordinate approval placeholders and normalization; public APIs should stay in run.py or
peer modules.
"""
from __future__ import annotations
from collections.abc import Sequence
from typing import Any
from openai.types.responses import ResponseFunctionToolCall
from ..agent import Agent
from ..items import ItemHelpers, RunItem, ToolApprovalItem, ToolCallOutputItem, TResponseInputItem
from ..tool import ToolOrigin
from .items import ReasoningItemIdPolicy, run_item_to_input_item
# --------------------------
# Public helpers
# --------------------------
def append_approval_error_output(
*,
generated_items: list[RunItem],
agent: Agent[Any],
tool_call: Any,
tool_name: str,
call_id: str | None,
message: str,
tool_origin: ToolOrigin | None = None,
) -> None:
"""Emit a synthetic tool output so users see why an approval failed."""
error_tool_call = _build_function_tool_call_for_approval_error(tool_call, tool_name, call_id)
generated_items.append(
ToolCallOutputItem(
output=message,
raw_item=ItemHelpers.tool_call_output_item(error_tool_call, message),
agent=agent,
tool_origin=tool_origin,
)
)
def filter_tool_approvals(interruptions: Sequence[Any]) -> list[ToolApprovalItem]:
"""Keep only approval items from a mixed interruption payload."""
return [item for item in interruptions if isinstance(item, ToolApprovalItem)]
def approvals_from_step(step: Any) -> list[ToolApprovalItem]:
"""Return approvals from a step that may or may not contain interruptions."""
interruptions = getattr(step, "interruptions", None)
if interruptions is None:
return []
return filter_tool_approvals(interruptions)
def append_input_items_excluding_approvals(
base_input: list[TResponseInputItem],
items: Sequence[RunItem],
reasoning_item_id_policy: ReasoningItemIdPolicy | None = None,
) -> None:
"""Append tool outputs to model input while skipping approval placeholders."""
for item in items:
converted = run_item_to_input_item(item, reasoning_item_id_policy)
if converted is None:
continue
base_input.append(converted)
# --------------------------
# Private helpers
# --------------------------
def _build_function_tool_call_for_approval_error(
tool_call: Any, tool_name: str, call_id: str | None
) -> ResponseFunctionToolCall:
"""Coerce raw tool call payloads into a normalized function_call for approval errors."""
if isinstance(tool_call, ResponseFunctionToolCall):
return tool_call
namespace = None
if isinstance(tool_call, dict):
candidate = tool_call.get("namespace")
if isinstance(candidate, str) and candidate:
namespace = candidate
else:
candidate = getattr(tool_call, "namespace", None)
if isinstance(candidate, str) and candidate:
namespace = candidate
kwargs: dict[str, Any] = {
"type": "function_call",
"name": tool_name,
"call_id": call_id or "unknown",
"status": "completed",
"arguments": "{}",
}
if namespace is not None:
kwargs["namespace"] = namespace
return ResponseFunctionToolCall(**kwargs)