diff --git a/src/agents/items.py b/src/agents/items.py index a4da32fe97..49f734b1d0 100644 --- a/src/agents/items.py +++ b/src/agents/items.py @@ -150,14 +150,7 @@ def _get_agent_via_weakref(self, attr_name: str, ref_name: str) -> Any: def to_input_item(self) -> TResponseInputItem: """Converts this item into an input item suitable for passing to the model.""" - if isinstance(self.raw_item, dict): - # We know that input items are dicts, so we can ignore the type error - return self.raw_item # type: ignore - elif isinstance(self.raw_item, BaseModel): - # All output items are Pydantic models that can be converted to input items. - return self.raw_item.model_dump(exclude_unset=True) # type: ignore - else: - raise AgentsException(f"Unexpected raw item type: {type(self.raw_item)}") + return _output_item_to_input_item(self.raw_item) @dataclass @@ -491,7 +484,7 @@ def to_input_item(self) -> TResponseInputItem: if isinstance(outcome, dict): if outcome.get("type") == "exit": entry["outcome"] = outcome - return cast(TResponseInputItem, payload) + return _output_item_to_input_item(payload) return super().to_input_item() diff --git a/tests/test_items_helpers.py b/tests/test_items_helpers.py index 264f631092..df96b65243 100644 --- a/tests/test_items_helpers.py +++ b/tests/test_items_helpers.py @@ -621,6 +621,85 @@ def test_to_input_items_strips_created_by_for_non_tool_search_items() -> None: assert all("created_by" not in item for item in input_items) +def test_tool_call_item_to_input_item_strips_created_by() -> None: + """RunItem replay must strip output-only created_by, matching ModelResponse.to_input_items.""" + agent = Agent(name="A") + call = ResponseFunctionToolCall.model_validate( + { + "id": "fc_1", + "arguments": "{}", + "call_id": "call_1", + "name": "lookup", + "type": "function_call", + "created_by": "server", + } + ) + item = ToolCallItem(agent=agent, raw_item=call) + replayed = item.to_input_item() + assert isinstance(replayed, dict) + assert replayed["type"] == "function_call" + assert "created_by" not in replayed + assert getattr(call, "created_by", None) == "server" + + +def test_tool_call_output_item_to_input_item_strips_created_by() -> None: + agent = Agent(name="A") + item = ToolCallOutputItem( + agent=agent, + raw_item={ + "type": "function_call_output", + "call_id": "call_1", + "output": "ok", + "created_by": "server", + }, + output="ok", + ) + replayed = item.to_input_item() + assert isinstance(replayed, dict) + assert replayed["type"] == "function_call_output" + assert "created_by" not in replayed + assert item.raw_item["created_by"] == "server" + + +def test_dict_shell_call_output_item_to_input_item_sanitizes_without_mutation() -> None: + agent = Agent(name="A") + raw_item = { + "type": "shell_call_output", + "call_id": "call_1", + "status": "completed", + "shell_output": "legacy", + "provider_data": {"provider": "value"}, + "created_by": "server", + "output": [ + { + "stdout": "ok", + "stderr": "", + "outcome": {"type": "exit", "exit_code": 0}, + "created_by": "server", + } + ], + } + original_chunk = raw_item["output"][0] + item = ToolCallOutputItem(agent=agent, raw_item=raw_item, output="ok") + + replayed = item.to_input_item() + + assert replayed == { + "type": "shell_call_output", + "call_id": "call_1", + "output": [ + { + "stdout": "ok", + "stderr": "", + "outcome": {"type": "exit", "exit_code": 0}, + } + ], + } + assert raw_item["created_by"] == "server" + assert original_chunk["created_by"] == "server" + assert raw_item["output"][0] is original_chunk + + def test_to_input_items_strips_nested_created_by_from_shell_call_output() -> None: """``shell_call_output`` carries ``created_by`` at the item level and inside each output chunk. diff --git a/tests/test_run_internal_items.py b/tests/test_run_internal_items.py index 2b991ea342..1cdc793213 100644 --- a/tests/test_run_internal_items.py +++ b/tests/test_run_internal_items.py @@ -898,6 +898,29 @@ def test_run_item_to_input_item_strips_tool_search_created_by() -> None: assert "created_by" not in converted_output +def test_run_item_to_input_item_strips_function_call_created_by() -> None: + agent = Agent(name="A") + tool_call = ToolCallItem( + agent=agent, + raw_item=ResponseFunctionToolCall.model_validate( + { + "id": "fc_1", + "arguments": "{}", + "call_id": "call_1", + "name": "lookup", + "type": "function_call", + "created_by": "server", + } + ), + ) + + converted = run_items.run_item_to_input_item(tool_call) + + assert isinstance(converted, dict) + assert converted["type"] == "function_call" + assert "created_by" not in converted + + def test_run_item_to_input_item_omits_tool_call_metadata() -> None: agent = Agent(name="A") tool_call = ToolCallItem(