diff --git a/src/agents/items.py b/src/agents/items.py index c761cc221f..7a383c03a0 100644 --- a/src/agents/items.py +++ b/src/agents/items.py @@ -373,8 +373,10 @@ def tool_name(self) -> str | None: def call_id(self) -> str | None: """Return the call identifier from the raw item, if available.""" if isinstance(self.raw_item, dict): - return self.raw_item.get("call_id") or self.raw_item.get("id") - return getattr(self.raw_item, "call_id", None) or getattr(self.raw_item, "id", None) + cid = self.raw_item.get("call_id") or self.raw_item.get("id") + else: + cid = getattr(self.raw_item, "call_id", None) or getattr(self.raw_item, "id", None) + return str(cid) if cid is not None else None ToolCallOutputTypes: TypeAlias = ( @@ -408,8 +410,9 @@ def call_id(self) -> str | None: """Return the call identifier from the raw item, if available.""" if isinstance(self.raw_item, dict): cid = self.raw_item.get("call_id") or self.raw_item.get("id") - return str(cid) if cid is not None else None - return getattr(self.raw_item, "call_id", None) or getattr(self.raw_item, "id", None) + else: + cid = getattr(self.raw_item, "call_id", None) or getattr(self.raw_item, "id", None) + return str(cid) if cid is not None else None def to_input_item(self) -> TResponseInputItem: """Converts the tool output into an input item for the next model turn. diff --git a/tests/test_items_helpers.py b/tests/test_items_helpers.py index 567fae94d9..8e653ffa47 100644 --- a/tests/test_items_helpers.py +++ b/tests/test_items_helpers.py @@ -3,6 +3,7 @@ import gc import json import weakref +from types import SimpleNamespace from typing import Any, cast from openai.types.responses.computer_action import Click as BatchedClick, Type as BatchedType @@ -323,6 +324,60 @@ def test_tool_call_output_item_preserves_function_output_structure() -> None: assert payload["output"] == raw_item["output"] +def test_tool_call_item_call_id_coerces_to_str() -> None: + agent = Agent(name="tester") + + # Dict raw item with a string call_id. + dict_call = ResponseFunctionToolCallParam( + id="f1", + arguments="{}", + call_id="c1", + name="func", + type="function_call", + ) + assert ToolCallItem(agent=agent, raw_item=cast(Any, dict_call)).call_id == "c1" + + # Object raw item with a non-string id falls back to a string. + object_call = ResponseFunctionToolCall( + id="f2", + arguments="{}", + call_id="c2", + name="func", + type="function_call", + ) + item = ToolCallItem(agent=agent, raw_item=object_call) + call_id = item.call_id + assert call_id == "c2" + assert isinstance(call_id, str) + + +def test_tool_call_item_call_id_missing_returns_none() -> None: + agent = Agent(name="tester") + raw_item = cast(Any, {"type": "function_call"}) + assert ToolCallItem(agent=agent, raw_item=raw_item).call_id is None + + +def test_tool_call_output_item_call_id_coerces_to_str() -> None: + agent = Agent(name="tester") + + # Dict raw item with a string call_id. + dict_raw = { + "type": "function_call_output", + "call_id": "call-keep", + "output": "value", + } + dict_item = ToolCallOutputItem(agent=agent, raw_item=cast(Any, dict_raw), output="value") + assert dict_item.call_id == "call-keep" + + # Object raw item whose call_id is a non-string value is coerced to str so the + # `str | None` return annotation is honored on both branches. + object_raw = cast(Any, SimpleNamespace(call_id=123)) + object_item = ToolCallOutputItem(agent=agent, raw_item=object_raw, output="value") + call_id = object_item.call_id + assert call_id == "123" + assert isinstance(call_id, str) + + def test_tool_call_output_item_constructs_function_call_output_dict(): # Build a simple ResponseFunctionToolCall. call = ResponseFunctionToolCall(