microsoft-agents-hosting-aiohttp 0.6.0.dev17__py3-none-any.whl → 0.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,9 @@ from .jwt_authorization_middleware import (
6
6
  jwt_authorization_middleware,
7
7
  jwt_authorization_decorator,
8
8
  )
9
- from .app.streaming import (
9
+
10
+ # Import streaming utilities from core for backward compatibility
11
+ from microsoft_agents.hosting.core.app.streaming import (
10
12
  Citation,
11
13
  CitationUtil,
12
14
  StreamingResponse,
@@ -3,8 +3,7 @@
3
3
 
4
4
  import asyncio
5
5
  import logging
6
- from typing import List, Optional, Callable, Literal, TYPE_CHECKING
7
- from dataclasses import dataclass
6
+ from typing import List, Optional, Callable, Literal
8
7
 
9
8
  from microsoft_agents.activity import (
10
9
  Activity,
@@ -1,102 +1,81 @@
1
1
  # Copyright (c) Microsoft Corporation. All rights reserved.
2
2
  # Licensed under the MIT License.
3
3
  import json
4
- from typing import List, Union, Type
5
4
 
6
5
  from aiohttp.web import RouteTableDef, Request, Response
7
6
 
8
- from microsoft_agents.activity import (
9
- AgentsModel,
10
- Activity,
11
- AttachmentData,
12
- ConversationParameters,
13
- Transcript,
14
- )
15
7
  from microsoft_agents.hosting.core import ChannelApiHandlerProtocol
8
+ from microsoft_agents.hosting.core.http import ChannelServiceRoutes
16
9
 
17
10
 
18
- async def deserialize_from_body(
19
- request: Request, target_model: Type[AgentsModel]
20
- ) -> Activity:
21
- if "application/json" in request.headers["Content-Type"]:
22
- body = await request.json()
23
- else:
24
- return Response(status=415)
11
+ class AiohttpRequestAdapter:
12
+ """Adapter for aiohttp requests to use with ChannelServiceRoutes."""
25
13
 
26
- return target_model.model_validate(body)
14
+ def __init__(self, request: Request):
15
+ self._request = request
27
16
 
17
+ @property
18
+ def method(self) -> str:
19
+ return self._request.method
28
20
 
29
- def get_serialized_response(
30
- model_or_list: Union[AgentsModel, List[AgentsModel]],
31
- ) -> Response:
32
- if isinstance(model_or_list, AgentsModel):
33
- json_obj = model_or_list.model_dump(
34
- mode="json", exclude_unset=True, by_alias=True
35
- )
36
- else:
37
- json_obj = [
38
- model.model_dump(mode="json", exclude_unset=True, by_alias=True)
39
- for model in model_or_list
40
- ]
21
+ @property
22
+ def headers(self):
23
+ return self._request.headers
24
+
25
+ async def json(self):
26
+ return await self._request.json()
27
+
28
+ def get_claims_identity(self):
29
+ return self._request.get("claims_identity")
41
30
 
42
- return Response(body=json.dumps(json_obj), content_type="application/json")
31
+ def get_path_param(self, name: str) -> str:
32
+ return self._request.match_info[name]
43
33
 
44
34
 
45
35
  def channel_service_route_table(
46
36
  handler: ChannelApiHandlerProtocol, base_url: str = ""
47
37
  ) -> RouteTableDef:
48
- # pylint: disable=unused-variable
38
+ """Create aiohttp route table for Channel Service API.
39
+
40
+ Args:
41
+ handler: The handler that implements the Channel API protocol.
42
+ base_url: Optional base URL prefix for all routes.
43
+
44
+ Returns:
45
+ RouteTableDef with all channel service routes.
46
+ """
49
47
  routes = RouteTableDef()
48
+ service_routes = ChannelServiceRoutes(handler, base_url)
49
+
50
+ def json_response(data: dict) -> Response:
51
+ return Response(body=json.dumps(data), content_type="application/json")
50
52
 
51
53
  @routes.post(base_url + "/v3/conversations/{conversation_id}/activities")
52
54
  async def send_to_conversation(request: Request):
53
- activity = await deserialize_from_body(request, Activity)
54
- result = await handler.on_send_to_conversation(
55
- request.get("claims_identity"),
56
- request.match_info["conversation_id"],
57
- activity,
55
+ result = await service_routes.send_to_conversation(
56
+ AiohttpRequestAdapter(request)
58
57
  )
59
-
60
- return get_serialized_response(result)
58
+ return json_response(result)
61
59
 
62
60
  @routes.post(
63
61
  base_url + "/v3/conversations/{conversation_id}/activities/{activity_id}"
64
62
  )
65
63
  async def reply_to_activity(request: Request):
66
- activity = await deserialize_from_body(request, Activity)
67
- result = await handler.on_reply_to_activity(
68
- request.get("claims_identity"),
69
- request.match_info["conversation_id"],
70
- request.match_info["activity_id"],
71
- activity,
72
- )
73
-
74
- return get_serialized_response(result)
64
+ result = await service_routes.reply_to_activity(AiohttpRequestAdapter(request))
65
+ return json_response(result)
75
66
 
76
67
  @routes.put(
77
68
  base_url + "/v3/conversations/{conversation_id}/activities/{activity_id}"
78
69
  )
79
70
  async def update_activity(request: Request):
80
- activity = await deserialize_from_body(request, Activity)
81
- result = await handler.on_update_activity(
82
- request.get("claims_identity"),
83
- request.match_info["conversation_id"],
84
- request.match_info["activity_id"],
85
- activity,
86
- )
87
-
88
- return get_serialized_response(result)
71
+ result = await service_routes.update_activity(AiohttpRequestAdapter(request))
72
+ return json_response(result)
89
73
 
90
74
  @routes.delete(
91
75
  base_url + "/v3/conversations/{conversation_id}/activities/{activity_id}"
92
76
  )
93
77
  async def delete_activity(request: Request):
94
- await handler.on_delete_activity(
95
- request.get("claims_identity"),
96
- request.match_info["conversation_id"],
97
- request.match_info["activity_id"],
98
- )
99
-
78
+ await service_routes.delete_activity(AiohttpRequestAdapter(request))
100
79
  return Response()
101
80
 
102
81
  @routes.get(
@@ -104,91 +83,61 @@ def channel_service_route_table(
104
83
  + "/v3/conversations/{conversation_id}/activities/{activity_id}/members"
105
84
  )
106
85
  async def get_activity_members(request: Request):
107
- result = await handler.on_get_activity_members(
108
- request.get("claims_identity"),
109
- request.match_info["conversation_id"],
110
- request.match_info["activity_id"],
86
+ result = await service_routes.get_activity_members(
87
+ AiohttpRequestAdapter(request)
111
88
  )
112
-
113
- return get_serialized_response(result)
89
+ return json_response(result)
114
90
 
115
91
  @routes.post(base_url + "/")
116
92
  async def create_conversation(request: Request):
117
- conversation_parameters = deserialize_from_body(request, ConversationParameters)
118
- result = await handler.on_create_conversation(
119
- request.get("claims_identity"), conversation_parameters
93
+ result = await service_routes.create_conversation(
94
+ AiohttpRequestAdapter(request)
120
95
  )
121
-
122
- return get_serialized_response(result)
96
+ return json_response(result)
123
97
 
124
98
  @routes.get(base_url + "/")
125
99
  async def get_conversation(request: Request):
126
- # TODO: continuation token? conversation_id?
127
- result = await handler.on_get_conversations(
128
- request.get("claims_identity"), None
129
- )
130
-
131
- return get_serialized_response(result)
100
+ result = await service_routes.get_conversations(AiohttpRequestAdapter(request))
101
+ return json_response(result)
132
102
 
133
103
  @routes.get(base_url + "/v3/conversations/{conversation_id}/members")
134
104
  async def get_conversation_members(request: Request):
135
- result = await handler.on_get_conversation_members(
136
- request.get("claims_identity"),
137
- request.match_info["conversation_id"],
105
+ result = await service_routes.get_conversation_members(
106
+ AiohttpRequestAdapter(request)
138
107
  )
139
-
140
- return get_serialized_response(result)
108
+ return json_response(result)
141
109
 
142
110
  @routes.get(base_url + "/v3/conversations/{conversation_id}/members/{member_id}")
143
111
  async def get_conversation_member(request: Request):
144
- result = await handler.on_get_conversation_member(
145
- request.get("claims_identity"),
146
- request.match_info["member_id"],
147
- request.match_info["conversation_id"],
112
+ result = await service_routes.get_conversation_member(
113
+ AiohttpRequestAdapter(request)
148
114
  )
149
-
150
- return get_serialized_response(result)
115
+ return json_response(result)
151
116
 
152
117
  @routes.get(base_url + "/v3/conversations/{conversation_id}/pagedmembers")
153
118
  async def get_conversation_paged_members(request: Request):
154
- # TODO: continuation token? page size?
155
- result = await handler.on_get_conversation_paged_members(
156
- request.get("claims_identity"),
157
- request.match_info["conversation_id"],
119
+ result = await service_routes.get_conversation_paged_members(
120
+ AiohttpRequestAdapter(request)
158
121
  )
159
-
160
- return get_serialized_response(result)
122
+ return json_response(result)
161
123
 
162
124
  @routes.delete(base_url + "/v3/conversations/{conversation_id}/members/{member_id}")
163
125
  async def delete_conversation_member(request: Request):
164
- result = await handler.on_delete_conversation_member(
165
- request.get("claims_identity"),
166
- request.match_info["conversation_id"],
167
- request.match_info["member_id"],
126
+ result = await service_routes.delete_conversation_member(
127
+ AiohttpRequestAdapter(request)
168
128
  )
169
-
170
- return get_serialized_response(result)
129
+ return json_response(result)
171
130
 
172
131
  @routes.post(base_url + "/v3/conversations/{conversation_id}/activities/history")
173
132
  async def send_conversation_history(request: Request):
174
- transcript = deserialize_from_body(request, Transcript)
175
- result = await handler.on_send_conversation_history(
176
- request.get("claims_identity"),
177
- request.match_info["conversation_id"],
178
- transcript,
133
+ result = await service_routes.send_conversation_history(
134
+ AiohttpRequestAdapter(request)
179
135
  )
180
-
181
- return get_serialized_response(result)
136
+ return json_response(result)
182
137
 
183
138
  @routes.post(base_url + "/v3/conversations/{conversation_id}/attachments")
184
139
  async def upload_attachment(request: Request):
185
- attachment_data = deserialize_from_body(request, AttachmentData)
186
- result = await handler.on_upload_attachment(
187
- request.get("claims_identity"),
188
- request.match_info["conversation_id"],
189
- attachment_data,
190
- )
191
-
192
- return get_serialized_response(result)
140
+ result = await service_routes.upload_attachment(AiohttpRequestAdapter(request))
141
+ return json_response(result)
193
142
 
194
143
  return routes
@@ -1,39 +1,47 @@
1
1
  # Copyright (c) Microsoft Corporation. All rights reserved.
2
2
  # Licensed under the MIT License.
3
- from traceback import format_exc
4
3
  from typing import Optional
5
4
 
6
- from aiohttp.web import (
7
- Request,
8
- Response,
9
- json_response,
10
- HTTPBadRequest,
11
- HTTPMethodNotAllowed,
12
- HTTPUnauthorized,
13
- HTTPUnsupportedMediaType,
14
- )
15
- from microsoft_agents.hosting.core import error_resources
16
- from microsoft_agents.hosting.core.authorization import (
17
- ClaimsIdentity,
18
- Connections,
19
- )
20
- from microsoft_agents.activity import (
21
- Activity,
22
- DeliveryModes,
23
- )
24
- from microsoft_agents.hosting.core import (
25
- Agent,
26
- ChannelServiceAdapter,
27
- ChannelServiceClientFactoryBase,
28
- MessageFactory,
29
- RestChannelServiceClientFactory,
30
- TurnContext,
5
+ from aiohttp.web import Request, Response, json_response
6
+
7
+ from microsoft_agents.hosting.core import Agent
8
+ from microsoft_agents.hosting.core.authorization import Connections
9
+ from microsoft_agents.hosting.core.http import (
10
+ HttpAdapterBase,
11
+ HttpResponse,
31
12
  )
13
+ from microsoft_agents.hosting.core import ChannelServiceClientFactoryBase
32
14
 
33
15
  from .agent_http_adapter import AgentHttpAdapter
34
16
 
35
17
 
36
- class CloudAdapter(ChannelServiceAdapter, AgentHttpAdapter):
18
+ class AiohttpRequestAdapter:
19
+ """Adapter to make aiohttp Request compatible with HttpRequestProtocol."""
20
+
21
+ def __init__(self, request: Request):
22
+ self._request = request
23
+
24
+ @property
25
+ def method(self) -> str:
26
+ return self._request.method
27
+
28
+ @property
29
+ def headers(self):
30
+ return self._request.headers
31
+
32
+ async def json(self):
33
+ return await self._request.json()
34
+
35
+ def get_claims_identity(self):
36
+ return self._request.get("claims_identity")
37
+
38
+ def get_path_param(self, name: str) -> str:
39
+ return self._request.match_info[name]
40
+
41
+
42
+ class CloudAdapter(HttpAdapterBase, AgentHttpAdapter):
43
+ """CloudAdapter for aiohttp web framework."""
44
+
37
45
  def __init__(
38
46
  self,
39
47
  *,
@@ -43,77 +51,43 @@ class CloudAdapter(ChannelServiceAdapter, AgentHttpAdapter):
43
51
  """
44
52
  Initializes a new instance of the CloudAdapter class.
45
53
 
54
+ :param connection_manager: Optional connection manager for OAuth.
46
55
  :param channel_service_client_factory: The factory to use to create the channel service client.
47
56
  """
57
+ super().__init__(
58
+ connection_manager=connection_manager,
59
+ channel_service_client_factory=channel_service_client_factory,
60
+ )
48
61
 
49
- async def on_turn_error(context: TurnContext, error: Exception):
50
- error_message = f"Exception caught : {error}"
51
- print(format_exc())
62
+ async def process(self, request: Request, agent: Agent) -> Optional[Response]:
63
+ """Process an aiohttp request.
52
64
 
53
- await context.send_activity(MessageFactory.text(error_message))
65
+ Args:
66
+ request: The aiohttp request.
67
+ agent: The agent to handle the request.
54
68
 
55
- # Send a trace activity
56
- await context.send_trace_activity(
57
- "OnTurnError Trace",
58
- error_message,
59
- "https://www.botframework.com/schemas/error",
60
- "TurnError",
69
+ Returns:
70
+ aiohttp Response object.
71
+ """
72
+ # Adapt request to protocol
73
+ adapted_request = AiohttpRequestAdapter(request)
74
+
75
+ # Process using base implementation
76
+ http_response: HttpResponse = await self.process_request(adapted_request, agent)
77
+
78
+ # Convert HttpResponse to aiohttp Response
79
+ return self._to_aiohttp_response(http_response)
80
+
81
+ @staticmethod
82
+ def _to_aiohttp_response(http_response: HttpResponse) -> Response:
83
+ """Convert HttpResponse to aiohttp Response."""
84
+ if http_response.body is not None:
85
+ return json_response(
86
+ data=http_response.body,
87
+ status=http_response.status_code,
88
+ headers=http_response.headers,
61
89
  )
62
-
63
- self.on_turn_error = on_turn_error
64
-
65
- channel_service_client_factory = (
66
- channel_service_client_factory
67
- or RestChannelServiceClientFactory(connection_manager)
90
+ return Response(
91
+ status=http_response.status_code,
92
+ headers=http_response.headers,
68
93
  )
69
-
70
- super().__init__(channel_service_client_factory)
71
-
72
- async def process(self, request: Request, agent: Agent) -> Optional[Response]:
73
- if not request:
74
- raise TypeError(str(error_resources.RequestRequired))
75
- if not agent:
76
- raise TypeError(str(error_resources.AgentRequired))
77
-
78
- if request.method == "POST":
79
- # Deserialize the incoming Activity
80
- if "application/json" in request.headers["Content-Type"]:
81
- body = await request.json()
82
- else:
83
- raise HTTPUnsupportedMediaType()
84
-
85
- activity: Activity = Activity.model_validate(body)
86
-
87
- # default to anonymous identity with no claims
88
- claims_identity: ClaimsIdentity = request.get(
89
- "claims_identity", ClaimsIdentity({}, False)
90
- )
91
-
92
- # A POST request must contain an Activity
93
- if (
94
- not activity.type
95
- or not activity.conversation
96
- or not activity.conversation.id
97
- ):
98
- raise HTTPBadRequest
99
-
100
- try:
101
- # Process the inbound activity with the agent
102
- invoke_response = await self.process_activity(
103
- claims_identity, activity, agent.on_turn
104
- )
105
-
106
- if (
107
- activity.type == "invoke"
108
- or activity.delivery_mode == DeliveryModes.expect_replies
109
- ):
110
- # Invoke and ExpectReplies cannot be performed async, the response must be written before the calling thread is released.
111
- return json_response(
112
- data=invoke_response.body, status=invoke_response.status
113
- )
114
-
115
- return Response(status=202)
116
- except PermissionError:
117
- raise HTTPUnauthorized
118
- else:
119
- raise HTTPMethodNotAllowed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: microsoft-agents-hosting-aiohttp
3
- Version: 0.6.0.dev17
3
+ Version: 0.7.0
4
4
  Summary: Integration library for Microsoft Agents with aiohttp
5
5
  Author: Microsoft Corporation
6
6
  License-Expression: MIT
@@ -15,7 +15,7 @@ Classifier: Operating System :: OS Independent
15
15
  Requires-Python: >=3.10
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
- Requires-Dist: microsoft-agents-hosting-core==0.6.0.dev17
18
+ Requires-Dist: microsoft-agents-hosting-core==0.7.0
19
19
  Requires-Dist: aiohttp>=3.11.11
20
20
  Dynamic: license-file
21
21
  Dynamic: requires-dist
@@ -39,11 +39,29 @@ This library is part of the **Microsoft 365 Agents SDK for Python** - a comprehe
39
39
  <th style="width:20%">Date</th>
40
40
  <th style="width:60%">Release Notes</th>
41
41
  </tr>
42
+ <tr>
43
+ <td>0.6.1</td>
44
+ <td>2025-12-01</td>
45
+ <td>
46
+ <a href="https://github.com/microsoft/Agents-for-python/blob/main/changelog.md#microsoft-365-agents-sdk-for-python---release-notes-v061">
47
+ 0.6.1 Release Notes
48
+ </a>
49
+ </td>
50
+ </tr>
51
+ <tr>
52
+ <td>0.6.0</td>
53
+ <td>2025-11-18</td>
54
+ <td>
55
+ <a href="https://github.com/microsoft/Agents-for-python/blob/main/changelog.md#microsoft-365-agents-sdk-for-python---release-notes-v060">
56
+ 0.6.0 Release Notes
57
+ </a>
58
+ </td>
59
+ </tr>
42
60
  <tr>
43
61
  <td>0.5.0</td>
44
62
  <td>2025-10-22</td>
45
63
  <td>
46
- <a href="https://github.com/microsoft/Agents-for-python/blob/main/changelog.md">
64
+ <a href="https://github.com/microsoft/Agents-for-python/blob/main/changelog.md#microsoft-365-agents-sdk-for-python---release-notes-v050">
47
65
  0.5.0 Release Notes
48
66
  </a>
49
67
  </td>
@@ -1,16 +1,16 @@
1
- microsoft_agents/hosting/aiohttp/__init__.py,sha256=MiCeImORsTFi9V2n-MIWJA53GzroDd-54G_UoH_5U0Y,664
1
+ microsoft_agents/hosting/aiohttp/__init__.py,sha256=WMrEQvFHm9m0iGU2XBLD6QHYYjYVcCCTjM25jQ_rczk,760
2
2
  microsoft_agents/hosting/aiohttp/_start_agent_process.py,sha256=RqKjQrYGRlzp05ssropmKCwI7T4j-glQP1zJtuOHBI0,950
3
3
  microsoft_agents/hosting/aiohttp/agent_http_adapter.py,sha256=U6GQVPPj-Vi2Kan8i2LDrRtU-M9FpfXUzFqzOqvVYoI,444
4
- microsoft_agents/hosting/aiohttp/channel_service_route_table.py,sha256=3JNmK63j0ELgqUAtqsOXFjajhRTMgf29cIGwLCDwnLE,6745
5
- microsoft_agents/hosting/aiohttp/cloud_adapter.py,sha256=UzSoZPGNI9VVY9vQx5E6BMgJAa0x2UUmuW2JGEiX9bc,3882
4
+ microsoft_agents/hosting/aiohttp/channel_service_route_table.py,sha256=Ln-wVWqAwHAlcKHoRnVjki4dDaYKMs_LsdbUGvmomiI,5141
5
+ microsoft_agents/hosting/aiohttp/cloud_adapter.py,sha256=J4ebzfE5iZKYPuYlema_j1DkzLQvB_TEa9ke8f0YSe0,2949
6
6
  microsoft_agents/hosting/aiohttp/jwt_authorization_middleware.py,sha256=26r7lK-umiohc5H7mC-m0yeHr-Tq7DoALO1ea0pi714,2327
7
7
  microsoft_agents/hosting/aiohttp/app/__init__.py,sha256=TioskqZet16twXOsI3X2snyLzmuyeKNtN2dySD1Xw7s,253
8
8
  microsoft_agents/hosting/aiohttp/app/streaming/__init__.py,sha256=G_VGmQ0m6TkHZsHjRV5HitaCOt2EBEjENIoBYabJMqM,292
9
9
  microsoft_agents/hosting/aiohttp/app/streaming/citation.py,sha256=ZGaMUOWxxoMplwRrkFsjnK7Z12V6rT5odE7qZCu-mP8,498
10
10
  microsoft_agents/hosting/aiohttp/app/streaming/citation_util.py,sha256=c95c3Y3genmFc0vSXppPaD1-ShFohAV1UABZnyJS_BQ,2478
11
- microsoft_agents/hosting/aiohttp/app/streaming/streaming_response.py,sha256=JSJfoQ1ARn-rWKHAABO2dA4soyRR2F8435mj0qioGKQ,14187
12
- microsoft_agents_hosting_aiohttp-0.6.0.dev17.dist-info/licenses/LICENSE,sha256=ws_MuBL-SCEBqPBFl9_FqZkaaydIJmxHrJG2parhU4M,1141
13
- microsoft_agents_hosting_aiohttp-0.6.0.dev17.dist-info/METADATA,sha256=rUnZUC9ttdjo69DUGYFVCplcZCdBnnBlEoYyuoeMXhA,8380
14
- microsoft_agents_hosting_aiohttp-0.6.0.dev17.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
- microsoft_agents_hosting_aiohttp-0.6.0.dev17.dist-info/top_level.txt,sha256=lWKcT4v6fTA_NgsuHdNvuMjSrkiBMXohn64ApY7Xi8A,17
16
- microsoft_agents_hosting_aiohttp-0.6.0.dev17.dist-info/RECORD,,
11
+ microsoft_agents/hosting/aiohttp/app/streaming/streaming_response.py,sha256=PzbqiZuLO1cCsOXsO9ZUt8IckZTheTPahi078o-H2hE,14138
12
+ microsoft_agents_hosting_aiohttp-0.7.0.dist-info/licenses/LICENSE,sha256=ws_MuBL-SCEBqPBFl9_FqZkaaydIJmxHrJG2parhU4M,1141
13
+ microsoft_agents_hosting_aiohttp-0.7.0.dist-info/METADATA,sha256=iHdR9_30TS3gA95ZxaCLvrE3ctV7XLD4b7GlQucz7sU,8945
14
+ microsoft_agents_hosting_aiohttp-0.7.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
+ microsoft_agents_hosting_aiohttp-0.7.0.dist-info/top_level.txt,sha256=lWKcT4v6fTA_NgsuHdNvuMjSrkiBMXohn64ApY7Xi8A,17
16
+ microsoft_agents_hosting_aiohttp-0.7.0.dist-info/RECORD,,