diff --git a/libraries/microsoft-agents-hosting-aiohttp/microsoft_agents/hosting/aiohttp/channel_service_route_table.py b/libraries/microsoft-agents-hosting-aiohttp/microsoft_agents/hosting/aiohttp/channel_service_route_table.py index 3a2acad49..eb9ace8da 100644 --- a/libraries/microsoft-agents-hosting-aiohttp/microsoft_agents/hosting/aiohttp/channel_service_route_table.py +++ b/libraries/microsoft-agents-hosting-aiohttp/microsoft_agents/hosting/aiohttp/channel_service_route_table.py @@ -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 @@ -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): diff --git a/libraries/microsoft-agents-hosting-aiohttp/microsoft_agents/hosting/aiohttp/cloud_adapter.py b/libraries/microsoft-agents-hosting-aiohttp/microsoft_agents/hosting/aiohttp/cloud_adapter.py index 88373953e..659163409 100644 --- a/libraries/microsoft-agents-hosting-aiohttp/microsoft_agents/hosting/aiohttp/cloud_adapter.py +++ b/libraries/microsoft-agents-hosting-aiohttp/microsoft_agents/hosting/aiohttp/cloud_adapter.py @@ -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. diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/http/_channel_service_routes.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/http/_channel_service_routes.py index 3c7e5bcbe..beca9fba7 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/http/_channel_service_routes.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/http/_channel_service_routes.py @@ -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, @@ -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. @@ -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: @@ -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 @@ -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]: """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") @@ -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( @@ -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 {} async def send_conversation_history(self, request: HttpRequestProtocol) -> dict: """Handle POST /v3/conversations/{conversation_id}/activities/history.""" diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/http/_http_adapter_base.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/http/_http_adapter_base.py index 4ea5438b6..ca84c0c20 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/http/_http_adapter_base.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/http/_http_adapter_base.py @@ -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) - super().__init__(channel_service_client_factory) + super().__init__(factory) async def process_request( self, request: HttpRequestProtocol, agent: Agent diff --git a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/http/_http_response.py b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/http/_http_response.py index 955ff2ad9..efe686574 100644 --- a/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/http/_http_response.py +++ b/libraries/microsoft-agents-hosting-core/microsoft_agents/hosting/core/http/_http_response.py @@ -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: diff --git a/libraries/microsoft-agents-hosting-fastapi/readme.md b/libraries/microsoft-agents-hosting-fastapi/readme.md index 7de12b5df..ecd6007e7 100644 --- a/libraries/microsoft-agents-hosting-fastapi/readme.md +++ b/libraries/microsoft-agents-hosting-fastapi/readme.md @@ -96,7 +96,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() @app.post("/api/messages")