diff --git a/src/agents/items.py b/src/agents/items.py index 50a017c221..64b1e8b1a7 100644 --- a/src/agents/items.py +++ b/src/agents/items.py @@ -364,17 +364,25 @@ class ToolCallItem(RunItemBase[Any]): @property def tool_name(self) -> str | None: - """Return the tool name from the raw item, if available.""" + """Return the tool name from the raw item when available.""" if isinstance(self.raw_item, dict): - return self.raw_item.get("name") - return getattr(self.raw_item, "name", None) + candidate = self.raw_item.get("name") or self.raw_item.get("tool_name") + else: + candidate = getattr(self.raw_item, "name", None) or getattr( + self.raw_item, "tool_name", None + ) + return str(candidate) if candidate is not None else None @property def call_id(self) -> str | None: - """Return the call identifier from the raw item, if available.""" + """Return the call identifier from the raw item when 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) + candidate = self.raw_item.get("call_id") or self.raw_item.get("id") + else: + candidate = getattr(self.raw_item, "call_id", None) or getattr( + self.raw_item, "id", None + ) + return str(candidate) if candidate is not None else None ToolCallOutputTypes: TypeAlias = ( @@ -405,11 +413,14 @@ class ToolCallOutputItem(RunItemBase[Any]): @property def call_id(self) -> str | None: - """Return the call identifier from the raw item, if available.""" + """Return the call identifier from the raw item when 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) + candidate = self.raw_item.get("call_id") or self.raw_item.get("id") + else: + candidate = getattr(self.raw_item, "call_id", None) or getattr( + self.raw_item, "id", None + ) + return str(candidate) if candidate 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/src/agents/sandbox/capabilities/skills.py b/src/agents/sandbox/capabilities/skills.py index 5d58c6703f..b579e7bede 100644 --- a/src/agents/sandbox/capabilities/skills.py +++ b/src/agents/sandbox/capabilities/skills.py @@ -205,8 +205,17 @@ async def load_skill( src_root = self._src_root(source_grants=source_grants) if src_root is None: raise SkillsConfigError( - message="lazy skill source directory is unavailable", - context={"skill_name": skill_name}, + message=( + "lazy skill source directory is unavailable; " + "LocalDirLazySkillSource expects a host filesystem path, not a path " + "inside the sandbox" + ), + context={ + "skill_name": skill_name, + "configured_source_path": None + if self.source.src is None + else str(self.source.src), + }, ) matches = [ diff --git a/tests/sandbox/capabilities/test_skills_capability.py b/tests/sandbox/capabilities/test_skills_capability.py index 2603077777..def1cee1c8 100644 --- a/tests/sandbox/capabilities/test_skills_capability.py +++ b/tests/sandbox/capabilities/test_skills_capability.py @@ -623,9 +623,19 @@ async def test_load_skill_rejects_missing_lazy_source_directory(self, tmp_path: ) ) - with pytest.raises(SkillsConfigError): + with pytest.raises(SkillsConfigError) as exc_info: await capability.load_skill("missing-skill") + assert ( + exc_info.value.message + == "lazy skill source directory is unavailable; " + "LocalDirLazySkillSource expects a host filesystem path, not a path inside the sandbox" + ) + assert exc_info.value.context == { + "skill_name": "missing-skill", + "configured_source_path": str(tmp_path / "missing-skills"), + } + @pytest.mark.asyncio async def test_load_skill_rejects_ambiguous_skill_name(self, tmp_path: Path) -> None: workspace_root = tmp_path / "workspace" diff --git a/tests/test_run_internal_items.py b/tests/test_run_internal_items.py index 019a8ededa..a1de09619b 100644 --- a/tests/test_run_internal_items.py +++ b/tests/test_run_internal_items.py @@ -15,6 +15,7 @@ from agents.items import ( ReasoningItem, ToolCallItem, + ToolCallOutputItem, ToolSearchCallItem, ToolSearchOutputItem, TResponseInputItem, @@ -550,6 +551,52 @@ def test_run_item_to_input_item_omits_tool_call_metadata() -> None: assert "title" not in result_dict +def test_tool_call_item_exposes_convenience_properties_for_typed_raw_items() -> None: + agent = Agent(name="A") + item = ToolCallItem( + agent=agent, + raw_item=ResponseFunctionToolCall( + id="fc_123", + call_id="call_123", + name="lookup_account", + arguments="{}", + type="function_call", + status="completed", + ), + ) + + assert item.tool_name == "lookup_account" + assert item.call_id == "call_123" + + +def test_tool_call_item_exposes_convenience_properties_for_dict_raw_items() -> None: + agent = Agent(name="A") + item = ToolCallItem( + agent=agent, + raw_item={ + "id": "fc_456", + "call_id": "call_456", + "name": "lookup_account", + "arguments": "{}", + "type": "function_call", + }, + ) + + assert item.tool_name == "lookup_account" + assert item.call_id == "call_456" + + +def test_tool_call_output_item_exposes_call_id_for_dict_raw_items() -> None: + agent = Agent(name="A") + item = ToolCallOutputItem( + agent=agent, + raw_item={"type": "function_call_output", "call_id": "call_456", "output": "ok"}, + output="ok", + ) + + assert item.call_id == "call_456" + + def test_normalize_input_items_for_api_strips_internal_tool_call_metadata() -> None: item = cast( TResponseInputItem,