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 @@ -225,6 +225,8 @@ async def send_chat_history_messages(

Args:
chat_messages: Sequence of Agent Framework ChatMessage objects to send.
Can be empty - the request will still be sent to register
the user message from turn_context.activity.text.
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 @@ -235,6 +237,12 @@ async def send_chat_history_messages(
Raises:
ValueError: If chat_messages or turn_context is None.

Note:
Even if chat_messages is empty or all messages are filtered during
conversion, the request will still be sent to the MCP platform. This
ensures the user message from turn_context.activity.text is registered
correctly for real-time threat protection.

Example:
>>> service = McpToolRegistrationService()
>>> messages = [ChatMessage(role=Role.USER, text="Hello")]
Expand All @@ -249,11 +257,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 +266,13 @@ 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
# Call core service even with empty history_messages to register
# the user message from turn_context.activity.text in the MCP platform.
if len(history_messages) == 0:
self._logger.warning("All messages were filtered out during conversion (empty content)")
return OperationResult.success()
self._logger.info(
"Empty history messages (either no input or all filtered), "
"still sending to register user message"
)

# 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 @@ -347,7 +347,8 @@ async def send_chat_history_messages(
and activity.text.
messages: List of OpenAI TResponseInputItem messages to send. Supports
UserMessage, AssistantMessage, SystemMessage, and other OpenAI
message types.
message types. Can be empty - the request will still be sent to
register the user message from turn_context.activity.text.
options: Optional ToolOptions for customization. If not provided,
uses default options with orchestrator_name="OpenAI".

Expand All @@ -359,6 +360,12 @@ async def send_chat_history_messages(
Raises:
ValueError: If turn_context is None or messages is None.

Note:
Even if messages is empty or all messages are filtered during conversion,
the request will still be sent to the MCP platform. This ensures the user
message from turn_context.activity.text is registered correctly for
real-time threat protection.

Example:
>>> from microsoft_agents_a365.tooling.extensions.openai import (
... McpToolRegistrationService
Expand All @@ -382,11 +389,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,9 +401,13 @@ async def send_chat_history_messages(
# Convert OpenAI messages to ChatHistoryMessage format
chat_history_messages = self._convert_openai_messages_to_chat_history(messages)

# Call core service even with empty chat_history_messages to register
# the user message from turn_context.activity.text in the MCP platform.
if len(chat_history_messages) == 0:
self._logger.warning("No messages could be converted to chat history format")
return OperationResult.success()
self._logger.info(
"Empty chat history messages (either no input or all filtered), "
"still sending to register user message"
)

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 @@ -583,6 +583,11 @@ async def send_chat_history(
turn_context.activity is None, or any of the required fields
(conversation.id, activity.id, activity.text) are missing or empty.

Note:
Even if chat_history_messages is empty, the request will still be sent to
the MCP platform. This ensures the user message from turn_context.activity.text
is registered correctly for real-time threat protection.

Example:
>>> from datetime import datetime, timezone
>>> from microsoft_agents_a365.tooling.models import ChatHistoryMessage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,17 +120,20 @@ 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_core_service(
self, service, mock_turn_context
):
"""Test that empty message list returns success with warning log."""
"""Test that empty message list still calls core service to register user message."""
# 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 even for empty messages to register the user message
service._mcp_server_configuration_service.send_chat_history.assert_called_once()
# Verify empty list was passed
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 +495,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_still_calls_core(
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 still calls core service to register user message."""
# Arrange - all messages have empty content
msg1 = Mock()
msg1.message_id = "msg-1"
Expand All @@ -517,8 +520,11 @@ 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 even when all messages are filtered out to register user message
service._mcp_server_configuration_service.send_chat_history.assert_called_once()
# Verify empty list was passed (all messages filtered)
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
22 changes: 17 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,26 @@ 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_core_service(
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 still calls core service to register user message."""
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
# Core service SHOULD be called even for empty messages to register the user message
mock_send.assert_called_once()
# Verify empty list was passed
call_args = mock_send.call_args
assert call_args.kwargs["chat_history_messages"] == []

# UV-04
@pytest.mark.asyncio
Expand Down