Skip to content
Closed
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 @@ -221,10 +221,12 @@ async def send_chat_history_messages(
Send chat history messages to the MCP platform for real-time threat protection.

This is the primary implementation method that handles message conversion
and delegation to the core tooling service.
and delegation to the core tooling service. Empty message lists are valid
and will be sent to the API.

Args:
chat_messages: Sequence of Agent Framework ChatMessage objects to send.
Empty lists are valid and will be sent to the API.
turn_context: TurnContext from the Agents SDK containing conversation info.
tool_options: Optional configuration for the request. Defaults to
AgentFramework-specific options if not provided.
Expand All @@ -249,11 +251,6 @@ async def send_chat_history_messages(
if turn_context is None:
raise ValueError("turn_context cannot be None")

# Handle empty messages - return success with warning
if len(chat_messages) == 0:
self._logger.warning("Empty message list provided to send_chat_history_messages")
return OperationResult.success()

self._logger.info(f"Send chat history initiated with {len(chat_messages)} messages")

# Use default options if not provided
Expand All @@ -263,10 +260,7 @@ async def send_chat_history_messages(
# Convert messages to ChatHistoryMessage format
history_messages = self._convert_chat_messages_to_history(chat_messages)

# Check if all messages were filtered out during conversion
if len(history_messages) == 0:
self._logger.warning("All messages were filtered out during conversion (empty content)")
return OperationResult.success()
self._logger.debug(f"Converted {len(history_messages)} messages to ChatHistoryMessage format")

# Delegate to core service
result = await self._mcp_server_configuration_service.send_chat_history(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -340,14 +340,15 @@ async def send_chat_history_messages(

This method accepts a list of OpenAI TResponseInputItem messages, converts
them to ChatHistoryMessage format, and sends them to the MCP platform.
Empty message lists are valid and will be sent to the API.

Args:
turn_context: TurnContext from the Agents SDK containing conversation info.
Must have a valid activity with conversation.id, activity.id,
and activity.text.
messages: List of OpenAI TResponseInputItem messages to send. Supports
UserMessage, AssistantMessage, SystemMessage, and other OpenAI
message types.
message types. Empty lists are valid and will be sent to the API.
options: Optional ToolOptions for customization. If not provided,
uses default options with orchestrator_name="OpenAI".

Expand Down Expand Up @@ -382,11 +383,6 @@ async def send_chat_history_messages(
if messages is None:
raise ValueError("messages cannot be None")

# Handle empty list as no-op
if len(messages) == 0:
self._logger.info("Empty message list provided, returning success")
return OperationResult.success()

self._logger.info(f"Sending {len(messages)} OpenAI messages as chat history")

# Set default options
Expand All @@ -399,10 +395,6 @@ async def send_chat_history_messages(
# Convert OpenAI messages to ChatHistoryMessage format
chat_history_messages = self._convert_openai_messages_to_chat_history(messages)

if len(chat_history_messages) == 0:
self._logger.warning("No messages could be converted to chat history format")
return OperationResult.success()

self._logger.debug(
f"Converted {len(chat_history_messages)} messages to ChatHistoryMessage format"
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,7 @@ async def send_chat_history(
Must have a valid activity with conversation.id, activity.id, and
activity.text.
chat_history_messages: List of ChatHistoryMessage objects representing the chat
history. Must be non-empty.
history. Empty lists are valid and will be sent to the API.
options: Optional ToolOptions instance containing optional parameters.

Returns:
Expand All @@ -578,7 +578,7 @@ async def send_chat_history(
On failure, returns OperationResult.failed() with error details.

Raises:
ValueError: If turn_context is None, chat_history_messages is None or empty,
ValueError: If turn_context is None, chat_history_messages is None,
turn_context.activity is None, or any of the required fields
(conversation.id, activity.id, activity.text) are missing or empty.

Expand All @@ -602,11 +602,6 @@ async def send_chat_history(
if chat_history_messages is None:
raise ValueError("chat_history_messages cannot be None")

# Handle empty messages - return success with warning (consistent with extension behavior)
if len(chat_history_messages) == 0:
self._logger.warning("Empty message list provided to send_chat_history")
return OperationResult.success()

# Extract required information from turn context
if not turn_context.activity:
raise ValueError("turn_context.activity cannot be None")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,17 +120,19 @@ async def test_send_chat_history_from_store_validates_turn_context_none(

@pytest.mark.asyncio
@pytest.mark.unit
async def test_send_chat_history_messages_empty_messages_returns_success(
async def test_send_chat_history_messages_empty_messages_calls_api(
self, service, mock_turn_context
):
"""Test that empty message list returns success with warning log."""
"""Test that empty message list is sent to the API."""
# Act
result = await service.send_chat_history_messages([], mock_turn_context)

# Assert
assert result.succeeded is True
# Core service should not be called for empty messages
service._mcp_server_configuration_service.send_chat_history.assert_not_called()
# Core service should be called with an empty list
service._mcp_server_configuration_service.send_chat_history.assert_called_once()
call_args = service._mcp_server_configuration_service.send_chat_history.call_args
assert call_args.kwargs["chat_history_messages"] == []

@pytest.mark.asyncio
@pytest.mark.unit
Expand Down Expand Up @@ -492,10 +494,10 @@ async def test_send_chat_history_messages_skips_messages_with_none_role(

@pytest.mark.asyncio
@pytest.mark.unit
async def test_send_chat_history_messages_all_filtered_returns_success(
async def test_send_chat_history_messages_all_filtered_calls_api_with_empty_list(
self, service, mock_turn_context, mock_role
):
"""Test that all messages filtered out returns success without calling core (CRM-006)."""
"""Test that all messages filtered out calls API with empty list (CRM-006)."""
# Arrange - all messages have empty content
msg1 = Mock()
msg1.message_id = "msg-1"
Expand All @@ -517,8 +519,10 @@ async def test_send_chat_history_messages_all_filtered_returns_success(

# Assert
assert result.succeeded is True
# Core service should not be called when all messages are filtered out
service._mcp_server_configuration_service.send_chat_history.assert_not_called()
# Core service should be called with an empty list when all messages are filtered out
service._mcp_server_configuration_service.send_chat_history.assert_called_once()
call_args = service._mcp_server_configuration_service.send_chat_history.call_args
assert call_args.kwargs["chat_history_messages"] == []

@pytest.mark.asyncio
@pytest.mark.unit
Expand Down
21 changes: 16 additions & 5 deletions tests/tooling/extensions/openai/test_send_chat_history.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,25 @@ async def test_send_chat_history_messages_validates_messages_none(
# UV-03
@pytest.mark.asyncio
@pytest.mark.unit
async def test_send_chat_history_messages_empty_list_returns_success(
async def test_send_chat_history_messages_empty_list_calls_api(
self, service, mock_turn_context
):
"""Test that empty message list returns success (no-op)."""
result = await service.send_chat_history_messages(mock_turn_context, [])
"""Test that empty message list is sent to the API."""
with patch.object(
service.config_service,
"send_chat_history",
new_callable=AsyncMock,
) as mock_send:
mock_send.return_value = OperationResult.success()

assert result.succeeded is True
assert len(result.errors) == 0
result = await service.send_chat_history_messages(mock_turn_context, [])

assert result.succeeded is True
assert len(result.errors) == 0
# Verify the API was called with an empty list
mock_send.assert_called_once()
call_args = mock_send.call_args
assert call_args.kwargs["chat_history_messages"] == []

# UV-04
@pytest.mark.asyncio
Expand Down
31 changes: 23 additions & 8 deletions tests/tooling/services/test_send_chat_history.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,14 +146,29 @@ async def test_send_chat_history_validates_chat_history_messages(
await service.send_chat_history(mock_turn_context, None)

@pytest.mark.asyncio
async def test_send_chat_history_empty_list_returns_success(self, service, mock_turn_context):
"""Test that send_chat_history returns success for empty list (CRM-008)."""
# Act
result = await service.send_chat_history(mock_turn_context, [])

# Assert - empty list should return success, not raise exception
assert result.succeeded is True
assert len(result.errors) == 0
async def test_send_chat_history_empty_list_calls_api(self, service, mock_turn_context):
"""Test that send_chat_history sends empty list to API (CRM-008)."""
# Arrange
mock_response = AsyncMock()
mock_response.status = 200
mock_response.text = AsyncMock(return_value="OK")

# Mock aiohttp.ClientSession
with patch("aiohttp.ClientSession") as mock_session:
mock_session_instance = MagicMock()
mock_post = AsyncMock()
mock_post.__aenter__.return_value = mock_response
mock_session_instance.post.return_value = mock_post
mock_session.return_value.__aenter__.return_value = mock_session_instance

# Act
result = await service.send_chat_history(mock_turn_context, [])

# Assert - empty list should call API and return success
assert result.succeeded is True
assert len(result.errors) == 0
# Verify the API was called
assert mock_session_instance.post.called

@pytest.mark.asyncio
async def test_send_chat_history_validates_activity(self, service, chat_history_messages):
Expand Down