Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions src/agents/items.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -491,6 +484,7 @@ def to_input_item(self) -> TResponseInputItem:
if isinstance(outcome, dict):
if outcome.get("type") == "exit":
entry["outcome"] = outcome
payload.pop("created_by", None)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Strip nested shell ownership metadata too

When a ToolCallOutputItem is constructed from an unsanitized dictionary-form shell_call_output, this removes only the top-level created_by; each entry under output can also contain created_by, so to_input_item() still returns a payload the Responses API rejects. The shared _output_item_to_input_item() path already rebuilds these chunks without that field, so apply the same nested sanitization here while retaining the shell-specific field removal.

AGENTS.md reference: AGENTS.md:L93-L93

Useful? React with 👍 / 👎.

return cast(TResponseInputItem, payload)

return super().to_input_item()
Expand Down
40 changes: 40 additions & 0 deletions tests/test_items_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,46 @@ 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_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.

Expand Down
23 changes: 23 additions & 0 deletions tests/test_run_internal_items.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Loading