Skip to content
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
import json

from aiohttp.web import RouteTableDef, Request, Response

Expand Down Expand Up @@ -47,8 +46,10 @@ def channel_service_route_table(
routes = RouteTableDef()
service_routes = ChannelServiceRoutes(handler, base_url)

def json_response(data: dict) -> Response:
return Response(body=json.dumps(data), content_type="application/json")
def json_response(data: dict | list[dict]) -> Response:
import json

return Response(text=json.dumps(data), content_type="application/json")

@routes.post(base_url + "/v3/conversations/{conversation_id}/activities")
async def send_to_conversation(request: Request):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ class CloudAdapter(HttpAdapterBase, AgentHttpAdapter):
def __init__(
self,
*,
connection_manager: Connections = None,
channel_service_client_factory: ChannelServiceClientFactoryBase = None,
connection_manager: Connections | None = None,
channel_service_client_factory: ChannelServiceClientFactoryBase | None = None,
):
"""
Initializes a new instance of the CloudAdapter class.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

"""Channel service route definitions (framework-agnostic logic)."""

from typing import Type
from typing import Type, TypeVar, overload

from microsoft_agents.activity import (
AgentsModel,
Expand All @@ -16,6 +16,8 @@

from ._http_request_protocol import HttpRequestProtocol

AgentsModelT = TypeVar("AgentsModelT", bound=AgentsModel)


class ChannelServiceRoutes:
"""Defines the Channel Service API routes and their handlers.
Expand All @@ -36,8 +38,8 @@ def __init__(self, handler: ChannelApiHandlerProtocol, base_url: str = ""):

@staticmethod
async def deserialize_from_body(
request: HttpRequestProtocol, target_model: Type[AgentsModel]
) -> AgentsModel:
request: HttpRequestProtocol, target_model: Type[AgentsModelT]
) -> AgentsModelT:
"""Deserialize request body to target model."""
content_type = request.headers.get("Content-Type", "")
if "application/json" not in content_type:
Expand All @@ -47,8 +49,16 @@ async def deserialize_from_body(
return target_model.model_validate(body)

@staticmethod
def serialize_model(model_or_list: AgentsModel | list[AgentsModel]) -> dict:
"""Serialize model or list of models to JSON-compatible dict."""
@overload
def serialize_model(model_or_list: AgentsModelT) -> dict: ...
@staticmethod
@overload
def serialize_model(model_or_list: list[AgentsModelT]) -> list[dict]: ...
@staticmethod
def serialize_model(
model_or_list: AgentsModelT | list[AgentsModelT],
) -> dict | list[dict]:
"""Serialize model or list of models to JSON-compatible dict or list of dicts."""
if isinstance(model_or_list, AgentsModel):
return model_or_list.model_dump(
mode="json", exclude_unset=True, by_alias=True
Expand Down Expand Up @@ -107,7 +117,7 @@ async def delete_activity(self, request: HttpRequestProtocol) -> None:
activity_id,
)

async def get_activity_members(self, request: HttpRequestProtocol) -> dict:
async def get_activity_members(self, request: HttpRequestProtocol) -> list[dict]:
Comment thread
rodrigobr-msft marked this conversation as resolved.
"""Handle GET /v3/conversations/{conversation_id}/activities/{activity_id}/members."""
conversation_id = request.get_path_param("conversation_id")
activity_id = request.get_path_param("activity_id")
Expand Down Expand Up @@ -136,7 +146,9 @@ async def get_conversations(self, request: HttpRequestProtocol) -> dict:
)
return self.serialize_model(result)

async def get_conversation_members(self, request: HttpRequestProtocol) -> dict:
async def get_conversation_members(
self, request: HttpRequestProtocol
) -> list[dict]:
"""Handle GET /v3/conversations/{conversation_id}/members."""
conversation_id = request.get_path_param("conversation_id")
result = await self.handler.on_get_conversation_members(
Expand Down Expand Up @@ -177,7 +189,9 @@ async def delete_conversation_member(self, request: HttpRequestProtocol) -> dict
conversation_id,
member_id,
)
return self.serialize_model(result)
if result:
return self.serialize_model(result)
return {}
Comment on lines +192 to +194

async def send_conversation_history(self, request: HttpRequestProtocol) -> dict:
"""Handle POST /v3/conversations/{conversation_id}/activities/history."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,18 @@ async def on_turn_error(context: TurnContext, error: Exception):

self.on_turn_error = on_turn_error

channel_service_client_factory = (
channel_service_client_factory
or RestChannelServiceClientFactory(connection_manager)
)
factory: ChannelServiceClientFactoryBase

if channel_service_client_factory:
factory = channel_service_client_factory
else:
if not connection_manager:
raise ValueError(
"HttpAdapterBase.__init__: Either channel_service_client_factory or connection_manager must be provided."
)
factory = RestChannelServiceClientFactory(connection_manager)
Comment thread
rodrigobr-msft marked this conversation as resolved.
Comment thread
rodrigobr-msft marked this conversation as resolved.

super().__init__(channel_service_client_factory)
super().__init__(factory)

async def process_request(
self, request: HttpRequestProtocol, agent: Agent
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def ok(body: Any = None) -> HttpResponse:
@staticmethod
def accepted() -> HttpResponse:
"""Create 202 Accepted response."""
return HttpResponse(status_code=202)
return HttpResponse(status_code=202, content_type=None)

@staticmethod
def json(body: Any, status_code: int = 200) -> HttpResponse:
Expand Down
3 changes: 2 additions & 1 deletion libraries/microsoft-agents-hosting-fastapi/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ from microsoft_agents.hosting.fastapi import start_agent_process, CloudAdapter
from microsoft_agents.hosting.core.app import AgentApplication

app = FastAPI()
adapter = CloudAdapter()
connection_manager = MsalConnectionManager(**agents_sdk_config)
adapter = CloudAdapter(connection_manager=connection_manager)
agent_app = AgentApplication()
Comment thread
rodrigobr-msft marked this conversation as resolved.

@app.post("/api/messages")
Expand Down
Loading