Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -2483,7 +2483,15 @@ def _shell_item_to_contents(self, item: Any, local_shell_tool_name: str | None)
)
shell_timeout_ms = getattr(action, "timeout_ms", None)
shell_max_output = getattr(action, "max_output_length", None)
is_local_environment = getattr(getattr(item, "environment", None), "type", None) == "local"
item_environment: object = getattr(item, "environment", None)
env_type: object = (
cast("Mapping[str, object]", item_environment).get("type")
if isinstance(item_environment, Mapping)
else getattr(item_environment, "type", None)
)
is_local_environment: bool = bool(
env_type == "local" or (item_environment is None and local_shell_tool_name is not None)
)
if (
local_shell_tool_name
and is_local_environment
Expand Down
127 changes: 126 additions & 1 deletion python/packages/openai/tests/openai/test_openai_chat_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2828,11 +2828,14 @@ def local_exec(command: str) -> str:
mock_action.timeout_ms = 60000
mock_action.max_output_length = 4096

mock_environment = MagicMock()
mock_environment.type = "container_reference"

mock_shell_call = MagicMock()
mock_shell_call.type = "shell_call"
mock_shell_call.call_id = "shell-call-1"
mock_shell_call.action = mock_action
mock_shell_call.environment = None
mock_shell_call.environment = mock_environment
mock_shell_call.status = "completed"

mock_response.output = [mock_shell_call]
Expand All @@ -2849,6 +2852,89 @@ def local_exec(command: str) -> str:
assert call_content.status == "completed"


def test_foundry_shell_call_without_environment_uses_registered_local_executor() -> None:
"""An unmarked shell call executes locally when a local shell executor is registered."""
client = OpenAIChatClient(model="test-model", api_key="test-key")

def local_exec(command: str) -> str:
return command

local_shell_tool = OpenAIChatClient.get_shell_tool(func=local_exec)

mock_response = MagicMock()
mock_response.output_parsed = None
mock_response.metadata = {}
mock_response.usage = None
mock_response.id = "test-id"
mock_response.model = "test-model"
mock_response.created_at = 1000000000
mock_response.status = "completed"
mock_response.incomplete = None

mock_action = MagicMock()
mock_action.commands = ["ls -la", "pwd"]
mock_action.timeout_ms = 60000
mock_action.max_output_length = 4096

mock_shell_call = MagicMock()
mock_shell_call.type = "shell_call"
mock_shell_call.id = "shell-item-1"
mock_shell_call.call_id = "shell-call-1"
mock_shell_call.action = mock_action
mock_shell_call.environment = None
mock_shell_call.status = "completed"

mock_response.output = [mock_shell_call]

response = client._parse_response_from_openai(mock_response, options={"tools": [local_shell_tool]}) # type: ignore[arg-type]

assert len(response.messages[0].contents) == 1
call_content = response.messages[0].contents[0]
assert call_content.type == "function_call"
assert call_content.call_id == "shell-call-1"
assert call_content.name == local_shell_tool.name
assert call_content.parse_arguments() == {"command": "ls -la\npwd"}
assert call_content.informational_only is False


def test_foundry_shell_call_without_environment_without_local_executor_remains_hosted() -> None:
"""An unmarked shell call remains hosted informational content when no local executor is registered."""
client = OpenAIChatClient(model="test-model", api_key="test-key")

mock_response = MagicMock()
mock_response.output_parsed = None
mock_response.metadata = {}
mock_response.usage = None
mock_response.id = "test-id"
mock_response.model = "test-model"
mock_response.created_at = 1000000000
mock_response.status = "completed"
mock_response.incomplete = None

mock_action = MagicMock()
mock_action.commands = ["ls -la", "pwd"]
mock_action.timeout_ms = 60000
mock_action.max_output_length = 4096

mock_shell_call = MagicMock()
mock_shell_call.type = "shell_call"
mock_shell_call.id = "shell-item-1"
mock_shell_call.call_id = "shell-call-1"
mock_shell_call.action = mock_action
mock_shell_call.environment = None
mock_shell_call.status = "completed"

mock_response.output = [mock_shell_call]

response = client._parse_response_from_openai(mock_response, options={}) # type: ignore[arg-type]

assert len(response.messages[0].contents) == 1
call_content = response.messages[0].contents[0]
assert call_content.type == "shell_tool_call"
assert call_content.call_id == "shell-call-1"
assert call_content.commands == ["ls -la", "pwd"]


def test_response_content_creation_with_shell_call_output() -> None:
"""Test _parse_response_from_openai with shell_call_output output."""
client = OpenAIChatClient(model="test-model", api_key="test-key")
Expand Down Expand Up @@ -4243,6 +4329,45 @@ def local_exec(command: str) -> str:
assert call_content.additional_properties["openai.responses.shell.output_type"] == "shell_call_output"


def test_parse_chunk_from_openai_shell_call_without_environment_emits_command() -> None:
"""An unmarked completed shell call emits an executable function call when local executor is registered."""
client = OpenAIChatClient(model="test-model", api_key="test-key")

def local_exec(command: str) -> str:
return command

local_shell_tool = OpenAIChatClient.get_shell_tool(func=local_exec, approval_mode="never_require")
function_call_ids: dict[int, tuple[str, str]] = {}

mock_action = MagicMock()
mock_action.commands = ["python --version"]
mock_action.timeout_ms = 30000

mock_item = MagicMock()
mock_item.type = "shell_call"
mock_item.id = "local-shell-item-1"
mock_item.call_id = "local-shell-call-1"
mock_item.action = mock_action
mock_item.environment = None
mock_item.status = "completed"

mock_event = MagicMock()
mock_event.type = "response.output_item.done"
mock_event.item = mock_item

update = client._parse_chunk_from_openai(
mock_event, options={"tools": [local_shell_tool]}, function_call_ids=function_call_ids
)

assert len(update.contents) == 1
call_content = update.contents[0]
assert call_content.type == "function_call"
assert call_content.call_id == "local-shell-call-1"
assert call_content.name == local_shell_tool.name
assert call_content.parse_arguments() == {"command": "python --version"}
assert call_content.additional_properties["openai.responses.shell.output_type"] == "shell_call_output"


def test_parse_chunk_from_openai_shell_call_output_added_defers_result() -> None:
"""An in-progress shell_call_output on output_item.added must emit nothing.

Expand Down
Loading