Skip to content

Commit 316215f

Browse files
committed
feat: add convenience properties (tool_name, call_id, arguments) to ToolCallItem and ToolCallOutputItem
ToolApprovalItem already exposes tool_name, call_id, and arguments as properties, but ToolCallItem and ToolCallOutputItem require users to reach into raw_item internals. This adds the same convenience accessors to both classes, following the existing ToolApprovalItem patterns. Closes #2886
1 parent 86739b1 commit 316215f

File tree

2 files changed

+102
-0
lines changed

2 files changed

+102
-0
lines changed

src/agents/items.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,37 @@ class ToolCallItem(RunItemBase[Any]):
358358
title: str | None = None
359359
"""Optional short display label if known at item creation time."""
360360

361+
@property
362+
def tool_name(self) -> str | None:
363+
"""Return the tool name from the raw item, if available."""
364+
if isinstance(self.raw_item, dict):
365+
return self.raw_item.get("name")
366+
return getattr(self.raw_item, "name", None)
367+
368+
@property
369+
def call_id(self) -> str | None:
370+
"""Return the call identifier from the raw item, if available."""
371+
if isinstance(self.raw_item, dict):
372+
return self.raw_item.get("call_id") or self.raw_item.get("id")
373+
return getattr(self.raw_item, "call_id", None) or getattr(self.raw_item, "id", None)
374+
375+
@property
376+
def arguments(self) -> str | None:
377+
"""Return the tool call arguments as a JSON string, if available."""
378+
candidate: Any | None = None
379+
if isinstance(self.raw_item, dict):
380+
candidate = self.raw_item.get("arguments")
381+
elif hasattr(self.raw_item, "arguments"):
382+
candidate = self.raw_item.arguments
383+
if candidate is None:
384+
return None
385+
if isinstance(candidate, str):
386+
return candidate
387+
try:
388+
return json.dumps(candidate)
389+
except (TypeError, ValueError):
390+
return str(candidate)
391+
361392

362393
ToolCallOutputTypes: TypeAlias = Union[
363394
FunctionCallOutput,
@@ -382,6 +413,14 @@ class ToolCallOutputItem(RunItemBase[Any]):
382413

383414
type: Literal["tool_call_output_item"] = "tool_call_output_item"
384415

416+
@property
417+
def call_id(self) -> str | None:
418+
"""Return the call identifier from the raw item, if available."""
419+
if isinstance(self.raw_item, dict):
420+
cid = self.raw_item.get("call_id") or self.raw_item.get("id")
421+
return str(cid) if cid is not None else None
422+
return getattr(self.raw_item, "call_id", None) or getattr(self.raw_item, "id", None)
423+
385424
def to_input_item(self) -> TResponseInputItem:
386425
"""Converts the tool output into an input item for the next model turn.
387426
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""Tests for convenience properties on ToolCallItem and ToolCallOutputItem."""
2+
3+
from __future__ import annotations
4+
5+
from openai.types.responses.response_function_tool_call import ResponseFunctionToolCall
6+
7+
from agents import Agent
8+
from agents.items import ToolCallItem, ToolCallOutputItem
9+
10+
11+
def test_tool_call_item_properties_from_function_call() -> None:
12+
raw = ResponseFunctionToolCall(
13+
id="fc_1",
14+
arguments='{"x": 1}',
15+
call_id="call_1",
16+
name="my_tool",
17+
type="function_call",
18+
)
19+
item = ToolCallItem(agent=Agent(name="test"), raw_item=raw)
20+
21+
assert item.tool_name == "my_tool"
22+
assert item.call_id == "call_1"
23+
assert item.arguments == '{"x": 1}'
24+
25+
26+
def test_tool_call_item_properties_from_dict() -> None:
27+
item = ToolCallItem(
28+
agent=Agent(name="test"),
29+
raw_item={"name": "search", "call_id": "c1", "arguments": '{"q": "hi"}'},
30+
)
31+
assert item.tool_name == "search"
32+
assert item.call_id == "c1"
33+
assert item.arguments == '{"q": "hi"}'
34+
35+
36+
def test_tool_call_item_call_id_falls_back_to_id() -> None:
37+
item = ToolCallItem(agent=Agent(name="test"), raw_item={"id": "fallback"})
38+
assert item.call_id == "fallback"
39+
40+
41+
def test_tool_call_item_returns_none_for_empty_raw_item() -> None:
42+
item = ToolCallItem(agent=Agent(name="test"), raw_item={})
43+
assert item.tool_name is None
44+
assert item.call_id is None
45+
assert item.arguments is None
46+
47+
48+
def test_tool_call_output_item_call_id_from_dict() -> None:
49+
item = ToolCallOutputItem(
50+
agent=Agent(name="test"),
51+
raw_item={"type": "function_call_output", "call_id": "call_xyz", "output": "ok"},
52+
output="ok",
53+
)
54+
assert item.call_id == "call_xyz"
55+
56+
57+
def test_tool_call_output_item_call_id_returns_none_when_missing() -> None:
58+
item = ToolCallOutputItem(
59+
agent=Agent(name="test"),
60+
raw_item={"type": "function_call_output", "output": "ok"},
61+
output="ok",
62+
)
63+
assert item.call_id is None

0 commit comments

Comments
 (0)