chore: import upstream snapshot with attribution
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:39:25 +08:00
commit db620d33df
5151 changed files with 925932 additions and 0 deletions
@@ -0,0 +1,424 @@
# Copyright (c) Microsoft. All rights reserved.
"""Tests for ChatKit to Agent Framework converter utilities."""
from unittest.mock import Mock
import pytest
from agent_framework import Message
from chatkit.types import InferenceOptions, UserMessageTextContent
from pydantic import AnyUrl
from agent_framework_chatkit import ThreadItemConverter, simple_to_agent_input
class TestThreadItemConverter:
"""Tests for ThreadItemConverter class."""
@pytest.fixture
def converter(self):
"""Create a ThreadItemConverter instance for testing."""
return ThreadItemConverter()
async def test_to_agent_input_none(self, converter):
"""Test converting empty list returns empty list."""
result = await converter.to_agent_input([])
assert result == []
async def test_to_agent_input_with_text(self, converter):
"""Test converting user message with text content."""
from datetime import datetime
from chatkit.types import UserMessageItem
input_item = UserMessageItem(
id="msg_1",
thread_id="thread_1",
created_at=datetime.now(),
type="user_message",
content=[UserMessageTextContent(text="Hello, how can you help me?")],
attachments=[],
inference_options=InferenceOptions(),
)
result = await converter.to_agent_input(input_item)
assert len(result) == 1
assert isinstance(result[0], Message)
assert result[0].role == "user"
assert result[0].text == "Hello, how can you help me?"
async def test_to_agent_input_empty_text(self, converter):
"""Test converting user message with empty or whitespace-only text."""
from datetime import datetime
from chatkit.types import UserMessageItem
input_item = UserMessageItem(
id="msg_1",
thread_id="thread_1",
created_at=datetime.now(),
type="user_message",
content=[UserMessageTextContent(text=" ")],
attachments=[],
inference_options=InferenceOptions(),
)
result = await converter.to_agent_input(input_item)
assert result == []
async def test_to_agent_input_no_content(self, converter):
"""Test converting user message with no content."""
from datetime import datetime
from chatkit.types import UserMessageItem
input_item = UserMessageItem(
id="msg_1",
thread_id="thread_1",
created_at=datetime.now(),
type="user_message",
content=[],
attachments=[],
inference_options=InferenceOptions(),
)
result = await converter.to_agent_input(input_item)
assert result == []
async def test_to_agent_input_multiple_content_parts(self, converter):
"""Test converting user message with multiple text content parts."""
from datetime import datetime
from chatkit.types import UserMessageItem
input_item = UserMessageItem(
id="msg_1",
thread_id="thread_1",
created_at=datetime.now(),
type="user_message",
content=[
UserMessageTextContent(text="Hello "),
UserMessageTextContent(text="world!"),
],
attachments=[],
inference_options=InferenceOptions(),
)
result = await converter.to_agent_input(input_item)
assert len(result) == 1
assert result[0].text == "Hello world!"
def test_hidden_context_to_input(self, converter):
"""Test converting hidden context item to Message."""
hidden_item = Mock()
hidden_item.content = "This is hidden context information"
result = converter.hidden_context_to_input(hidden_item)
assert isinstance(result, Message)
assert result.role == "system"
assert result.text == "<HIDDEN_CONTEXT>This is hidden context information</HIDDEN_CONTEXT>"
def test_tag_to_message_content(self, converter):
"""Test converting tag to message content."""
from chatkit.types import UserMessageTagContent
tag = UserMessageTagContent(
type="input_tag",
id="tag_1",
text="john",
data={"name": "John Doe"},
interactive=False,
)
result = converter.tag_to_message_content(tag)
assert result.type == "text"
# Since data is a dict, getattr won't work, so it will fall back to text
assert result.text == "<TAG>Name:john</TAG>"
def test_tag_to_message_content_no_name(self, converter):
"""Test converting tag with no name to message content."""
from chatkit.types import UserMessageTagContent
tag = UserMessageTagContent(
type="input_tag",
id="tag_2",
text="jane",
data={},
interactive=False,
)
result = converter.tag_to_message_content(tag)
assert result.type == "text"
assert result.text == "<TAG>Name:jane</TAG>"
async def test_attachment_to_message_content_file_without_fetcher(self, converter):
"""Test that FileAttachment without data fetcher returns None."""
from chatkit.types import FileAttachment
attachment = FileAttachment(
id="file_123",
name="document.pdf",
mime_type="application/pdf",
type="file",
)
result = await converter.attachment_to_message_content(attachment)
assert result is None
async def test_attachment_to_message_content_image_with_preview_url(self, converter):
"""Test that ImageAttachment with preview_url creates UriContent."""
from chatkit.types import ImageAttachment
attachment = ImageAttachment(
id="img_123",
name="photo.jpg",
mime_type="image/jpeg",
type="image",
preview_url=AnyUrl("https://example.com/photo.jpg"),
)
result = await converter.attachment_to_message_content(attachment)
assert result.type == "uri"
assert result.uri == "https://example.com/photo.jpg"
assert result.media_type == "image/jpeg"
async def test_attachment_to_message_content_with_data_fetcher(self):
"""Test attachment conversion with data fetcher."""
from chatkit.types import FileAttachment
# Mock data fetcher
async def fetch_data(attachment_id: str) -> bytes:
return b"file content data"
converter = ThreadItemConverter(attachment_data_fetcher=fetch_data)
attachment = FileAttachment(
id="file_123",
name="document.pdf",
mime_type="application/pdf",
type="file",
)
result = await converter.attachment_to_message_content(attachment)
assert result is not None
assert result.type == "data"
assert result.media_type == "application/pdf"
async def test_to_agent_input_with_image_attachment(self):
"""Test converting user message with text and image attachment."""
from datetime import datetime
from chatkit.types import ImageAttachment, UserMessageItem
attachment = ImageAttachment(
id="img_123",
name="photo.jpg",
mime_type="image/jpeg",
type="image",
preview_url=AnyUrl("https://example.com/photo.jpg"),
)
input_item = UserMessageItem(
id="msg_1",
thread_id="thread_1",
created_at=datetime.now(),
type="user_message",
content=[UserMessageTextContent(text="Check out this photo!")],
attachments=[attachment],
inference_options=InferenceOptions(),
)
converter = ThreadItemConverter()
result = await converter.to_agent_input(input_item)
assert len(result) == 1
message = result[0]
assert message.role == "user"
assert len(message.contents) == 2
# First content should be text
assert message.contents[0].type == "text"
assert message.contents[0].text == "Check out this photo!"
# Second content should be UriContent for the image
assert message.contents[1].type == "uri"
assert message.contents[1].uri == "https://example.com/photo.jpg"
assert message.contents[1].media_type == "image/jpeg"
async def test_to_agent_input_with_file_attachment_and_fetcher(self):
"""Test converting user message with file attachment using data fetcher."""
from datetime import datetime
from chatkit.types import FileAttachment, UserMessageItem
attachment = FileAttachment(
id="file_123",
name="report.pdf",
mime_type="application/pdf",
type="file",
)
input_item = UserMessageItem(
id="msg_1",
thread_id="thread_1",
created_at=datetime.now(),
type="user_message",
content=[UserMessageTextContent(text="Here's the document")],
attachments=[attachment],
inference_options=InferenceOptions(),
)
# Create converter with data fetcher
async def fetch_data(attachment_id: str) -> bytes:
return b"PDF content data"
converter = ThreadItemConverter(attachment_data_fetcher=fetch_data)
result = await converter.to_agent_input(input_item)
assert len(result) == 1
message = result[0]
assert len(message.contents) == 2
# First content should be text
assert message.contents[0].type == "text"
# Second content should be DataContent for the file
assert message.contents[1].type == "data"
assert message.contents[1].media_type == "application/pdf"
def test_task_to_input(self, converter):
"""Test converting TaskItem to Message."""
from datetime import datetime
from chatkit.types import CustomTask, TaskItem
task_item = TaskItem(
id="task_1",
thread_id="thread_1",
created_at=datetime.now(),
type="task",
task=CustomTask(type="custom", title="Analysis", content="Analyzed the data"),
)
result = converter.task_to_input(task_item)
assert isinstance(result, Message)
assert result.role == "user"
assert "Analysis: Analyzed the data" in result.text
assert "<Task>" in result.text
def test_task_to_input_no_custom_task(self, converter):
"""Test that non-custom tasks return None."""
from datetime import datetime
from chatkit.types import TaskItem, ThoughtTask
task_item = TaskItem(
id="task_1",
thread_id="thread_1",
created_at=datetime.now(),
type="task",
task=ThoughtTask(type="thought", title="Think", content="Thinking..."),
)
result = converter.task_to_input(task_item)
assert result is None
def test_workflow_to_input(self, converter):
"""Test converting WorkflowItem to ChatMessages."""
from datetime import datetime
from chatkit.types import CustomTask, Workflow, WorkflowItem
workflow_item = WorkflowItem(
id="wf_1",
thread_id="thread_1",
created_at=datetime.now(),
type="workflow",
workflow=Workflow(
type="custom",
tasks=[
CustomTask(type="custom", title="Step 1", content="First step"),
CustomTask(type="custom", title="Step 2", content="Second step"),
],
),
)
result = converter.workflow_to_input(workflow_item)
assert isinstance(result, list)
assert len(result) == 2
assert all(isinstance(msg, Message) for msg in result)
assert "Step 1: First step" in result[0].text
assert "Step 2: Second step" in result[1].text
def test_workflow_to_input_empty(self, converter):
"""Test that workflows with no custom tasks return None."""
from datetime import datetime
from chatkit.types import Workflow, WorkflowItem
workflow_item = WorkflowItem(
id="wf_1",
thread_id="thread_1",
created_at=datetime.now(),
type="workflow",
workflow=Workflow(type="custom", tasks=[]),
)
result = converter.workflow_to_input(workflow_item)
assert result is None
def test_widget_to_input(self, converter):
"""Test converting WidgetItem to Message."""
from datetime import datetime
from chatkit.types import WidgetItem
from chatkit.widgets import Card, Text # ty: ignore[deprecated]
widget_item = WidgetItem(
id="widget_1",
thread_id="thread_1",
created_at=datetime.now(),
type="widget",
widget=Card(key="card1", children=[Text(value="Hello")]), # ty: ignore[deprecated]
)
result = converter.widget_to_input(widget_item)
assert isinstance(result, Message)
assert result.role == "user"
assert "widget_1" in result.text
assert "graphical UI widget" in result.text
class TestSimpleToAgentInput:
"""Tests for simple_to_agent_input helper function."""
async def test_simple_to_agent_input_empty_list(self):
"""Test simple conversion with empty list."""
result = await simple_to_agent_input([])
assert result == []
async def test_simple_to_agent_input_with_text(self):
"""Test simple conversion with text content."""
from datetime import datetime
from chatkit.types import UserMessageItem
input_item = UserMessageItem(
id="msg_1",
thread_id="thread_1",
created_at=datetime.now(),
type="user_message",
content=[UserMessageTextContent(text="Test message")],
attachments=[],
inference_options=InferenceOptions(),
)
result = await simple_to_agent_input(input_item)
assert len(result) == 1
assert isinstance(result[0], Message)
assert result[0].role == "user"
assert result[0].text == "Test message"
@@ -0,0 +1,156 @@
# Copyright (c) Microsoft. All rights reserved.
"""Tests for Agent Framework to ChatKit streaming utilities."""
from collections.abc import AsyncIterator
from unittest.mock import Mock
from agent_framework import AgentResponseUpdate, Content
from chatkit.types import (
AssistantMessageContent,
AssistantMessageContentPartTextDelta,
AssistantMessageItem,
ThreadItemAddedEvent,
ThreadItemDoneEvent,
ThreadItemUpdated,
)
from agent_framework_chatkit import stream_agent_response
class TestStreamAgentResponse:
"""Tests for stream_agent_response function."""
async def test_stream_empty_response(self):
"""Test streaming empty response."""
async def empty_stream() -> AsyncIterator[AgentResponseUpdate]:
return
yield # Make it a generator
events = []
async for event in stream_agent_response(empty_stream(), thread_id="test_thread"):
events.append(event)
assert len(events) == 0
async def test_stream_single_text_update(self):
"""Test streaming single text update."""
async def single_update_stream():
yield AgentResponseUpdate(role="assistant", contents=[Content.from_text(text="Hello world")])
events = []
async for event in stream_agent_response(single_update_stream(), thread_id="test_thread"):
events.append(event)
# Should have: item_added, item_updated (delta), item_done
assert len(events) == 3
# Check event types
assert isinstance(events[0], ThreadItemAddedEvent)
assert isinstance(events[1], ThreadItemUpdated)
assert isinstance(events[2], ThreadItemDoneEvent)
# Check delta event
assert isinstance(events[1].update, AssistantMessageContentPartTextDelta)
assert events[1].update.delta == "Hello world"
# Check final message content
assert isinstance(events[2].item, AssistantMessageItem)
assert len(events[2].item.content) == 1
assert isinstance(events[2].item.content[0], AssistantMessageContent)
assert events[2].item.content[0].text == "Hello world"
async def test_stream_multiple_text_updates(self):
"""Test streaming multiple text updates."""
async def multiple_updates_stream():
yield AgentResponseUpdate(role="assistant", contents=[Content.from_text(text="Hello ")])
yield AgentResponseUpdate(role="assistant", contents=[Content.from_text(text="world!")])
events = []
async for event in stream_agent_response(multiple_updates_stream(), thread_id="test_thread"):
events.append(event)
# Should have: item_added, item_updated (delta 1), item_updated (delta 2), item_done
assert len(events) == 4
# Check event types
assert isinstance(events[0], ThreadItemAddedEvent)
assert isinstance(events[1], ThreadItemUpdated)
assert isinstance(events[2], ThreadItemUpdated)
assert isinstance(events[3], ThreadItemDoneEvent)
# Check delta events
assert isinstance(events[1].update, AssistantMessageContentPartTextDelta)
assert isinstance(events[2].update, AssistantMessageContentPartTextDelta)
assert events[1].update.delta == "Hello "
assert events[2].update.delta == "world!"
# Check final accumulated text
final_message_event = events[-1]
assert isinstance(final_message_event, ThreadItemDoneEvent)
assert isinstance(final_message_event.item, AssistantMessageItem)
assert isinstance(final_message_event.item.content[0], AssistantMessageContent)
assert final_message_event.item.content[0].text == "Hello world!"
async def test_stream_with_custom_id_generator(self):
"""Test streaming with custom ID generator."""
def custom_id_generator(item_type: str) -> str:
return f"custom_{item_type}_123"
async def single_update_stream():
yield AgentResponseUpdate(role="assistant", contents=[Content.from_text(text="Test")])
events = []
async for event in stream_agent_response(
single_update_stream(), thread_id="test_thread", generate_id=custom_id_generator
):
events.append(event)
# Check that custom IDs are used
message_added_event = events[0]
assert isinstance(message_added_event, ThreadItemAddedEvent)
assert message_added_event.item.id == "custom_msg_123"
async def test_stream_empty_content_updates(self):
"""Test streaming updates with empty content."""
async def empty_content_stream():
yield AgentResponseUpdate(role="assistant", contents=[])
yield AgentResponseUpdate(role="assistant", contents=None)
events = []
async for event in stream_agent_response(empty_content_stream(), thread_id="test_thread"):
events.append(event)
# Should have item_added and item_done
assert len(events) == 2
assert isinstance(events[0], ThreadItemAddedEvent)
assert isinstance(events[1], ThreadItemDoneEvent)
# Final message should have empty content
assert isinstance(events[1].item, AssistantMessageItem)
assert len(events[1].item.content) == 0
async def test_stream_non_text_content(self):
"""Test streaming updates with non-text content."""
# Mock a content object without text attribute
non_text_content = Mock(spec=Content)
non_text_content.type = "image"
# Don't set text attribute
non_text_content.text = None
async def non_text_stream():
yield AgentResponseUpdate(role="assistant", contents=[non_text_content])
events = []
async for event in stream_agent_response(non_text_stream(), thread_id="test_thread"):
events.append(event)
# Should have item_added and item_done, but no content since no text
assert len(events) == 2
assert isinstance(events[0], ThreadItemAddedEvent)
assert isinstance(events[1], ThreadItemDoneEvent)