microsoft-agents-hosting-msteams 1.7.0.dev9__tar.gz → 1.8.0.dev0__tar.gz

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.
Files changed (55) hide show
  1. {microsoft_agents_hosting_msteams-1.7.0.dev9/microsoft_agents_hosting_msteams.egg-info → microsoft_agents_hosting_msteams-1.8.0.dev0}/PKG-INFO +48 -5
  2. microsoft_agents_hosting_msteams-1.8.0.dev0/VERSION.txt +1 -0
  3. microsoft_agents_hosting_msteams-1.8.0.dev0/microsoft_agents/hosting/msteams/_teams_api_client.py +132 -0
  4. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/_utils.py +40 -0
  5. microsoft_agents_hosting_msteams-1.8.0.dev0/microsoft_agents/hosting/msteams/models/__init__.py +7 -0
  6. microsoft_agents_hosting_msteams-1.8.0.dev0/microsoft_agents/hosting/msteams/models/quoted_reply.py +24 -0
  7. microsoft_agents_hosting_msteams-1.8.0.dev0/microsoft_agents/hosting/msteams/models/targeted_message_info.py +12 -0
  8. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/teams_activity.py +55 -1
  9. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/teams_agent_extension.py +8 -0
  10. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/teams_turn_context.py +77 -24
  11. microsoft_agents_hosting_msteams-1.7.0.dev9/readme.md → microsoft_agents_hosting_msteams-1.8.0.dev0/microsoft_agents_hosting_msteams.egg-info/PKG-INFO +69 -3
  12. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents_hosting_msteams.egg-info/SOURCES.txt +3 -0
  13. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents_hosting_msteams.egg-info/requires.txt +1 -1
  14. microsoft_agents_hosting_msteams-1.7.0.dev9/PKG-INFO → microsoft_agents_hosting_msteams-1.8.0.dev0/readme.md +46 -26
  15. microsoft_agents_hosting_msteams-1.7.0.dev9/VERSION.txt +0 -1
  16. microsoft_agents_hosting_msteams-1.7.0.dev9/microsoft_agents/hosting/msteams/_teams_api_client.py +0 -76
  17. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/LICENSE +0 -0
  18. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/MANIFEST.in +0 -0
  19. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/__init__.py +0 -0
  20. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/_graph.py +0 -0
  21. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/channel/__init__.py +0 -0
  22. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/channel/channel.py +0 -0
  23. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/channel/route_handlers.py +0 -0
  24. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/config/__init__.py +0 -0
  25. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/config/config.py +0 -0
  26. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/config/route_handlers.py +0 -0
  27. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/errors/__init__.py +0 -0
  28. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/errors/error_resources.py +0 -0
  29. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/file_consent/__init__.py +0 -0
  30. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/file_consent/file_consent.py +0 -0
  31. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/file_consent/route_handlers.py +0 -0
  32. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/meeting/__init__.py +0 -0
  33. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/meeting/meeting.py +0 -0
  34. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/meeting/route_handlers.py +0 -0
  35. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/message/__init__.py +0 -0
  36. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/message/message.py +0 -0
  37. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/message/route_handlers.py +0 -0
  38. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/message_extension/__init__.py +0 -0
  39. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/message_extension/message_extension.py +0 -0
  40. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/message_extension/route_handlers.py +0 -0
  41. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/proactive_service_endpoints.py +0 -0
  42. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/py.typed +0 -0
  43. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/route_handlers.py +0 -0
  44. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/task_module/__init__.py +0 -0
  45. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/task_module/route_handlers.py +0 -0
  46. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/task_module/task_module.py +0 -0
  47. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/team/__init__.py +0 -0
  48. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/team/route_handlers.py +0 -0
  49. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/team/team.py +0 -0
  50. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents/hosting/msteams/type_defs.py +0 -0
  51. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents_hosting_msteams.egg-info/dependency_links.txt +0 -0
  52. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/microsoft_agents_hosting_msteams.egg-info/top_level.txt +0 -0
  53. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/pyproject.toml +0 -0
  54. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/setup.cfg +0 -0
  55. {microsoft_agents_hosting_msteams-1.7.0.dev9 → microsoft_agents_hosting_msteams-1.8.0.dev0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: microsoft-agents-hosting-msteams
3
- Version: 1.7.0.dev9
3
+ Version: 1.8.0.dev0
4
4
  Summary: Integration library for Microsoft Agents with Teams
5
5
  Author: Microsoft Corporation
6
6
  License-Expression: MIT
@@ -14,7 +14,7 @@ Classifier: Operating System :: OS Independent
14
14
  Requires-Python: >=3.11
15
15
  Description-Content-Type: text/markdown
16
16
  License-File: LICENSE
17
- Requires-Dist: microsoft-agents-hosting-core==1.7.0.dev9
17
+ Requires-Dist: microsoft-agents-hosting-core==1.8.0.dev0
18
18
  Requires-Dist: aiohttp>=3.11.11
19
19
  Requires-Dist: microsoft-teams-api==2.1.0
20
20
  Requires-Dist: msgraph-sdk>=1.58.0
@@ -41,6 +41,15 @@ This library is part of the **Microsoft 365 Agents SDK for Python** — a compre
41
41
  <th style="width:20%">Date</th>
42
42
  <th style="width:60%">Release Notes</th>
43
43
  </tr>
44
+ <tr>
45
+ <td>1.8.0</td>
46
+ <td>2026-10-01</td>
47
+ <td>
48
+ <a href="https://github.com/microsoft/Agents-for-python/blob/main/changelog.md#microsoft-365-agents-sdk-for-python---release-notes-v180">
49
+ 1.8.0 Release Notes
50
+ </a>
51
+ </td>
52
+ </tr>
44
53
  <tr>
45
54
  <td>1.7.0</td>
46
55
  <td>2026-09-17</td>
@@ -253,6 +262,10 @@ context.activity.notify_user(alert_in_meeting=True)
253
262
 
254
263
  # Attach a feedback loop to a message being sent (Copilot scenarios)
255
264
  context.activity.enable_feedback_loop()
265
+
266
+ # Inspect quoted messages or targeted-message metadata
267
+ quoted_messages = context.activity.get_quoted_messages()
268
+ targeted_message = context.activity.get_targeted_message_info()
256
269
  ```
257
270
 
258
271
  #### `context.api_client` → `ApiClient`
@@ -261,13 +274,43 @@ Direct access to the Teams REST API client, pre-authenticated for the current tu
261
274
 
262
275
  #### Sending targeted activities
263
276
 
264
- `TeamsTurnContext` adds two methods for sending activities that target a specific user in a meeting:
277
+ Use `TeamsTurnContext.send_targeted_activity()` to send a private activity to a
278
+ specific user in a meeting or group conversation. Pass either the recipient ID
279
+ or a `ChannelAccount`:
265
280
 
266
281
  ```python
267
- await context.send_targeted_activity(activity)
268
- await context.send_targeted_activities([activity1, activity2])
282
+ await context.send_targeted_activity(
283
+ "Only you can see this message.",
284
+ recipient.id,
285
+ )
269
286
  ```
270
287
 
288
+ Replies to inbound targeted activities automatically include the prompt-preview
289
+ metadata required by Teams.
290
+
291
+ #### Sending quoted replies
292
+
293
+ Use `TeamsActivity.add_quoted_reply()` to quote an existing message:
294
+
295
+ ```python
296
+ from microsoft_agents.activity import ActivityTypes
297
+ from microsoft_agents.hosting.msteams import TeamsActivity
298
+
299
+ message_id = context.activity.id
300
+ if not message_id:
301
+ raise ValueError("Message ID is required for a quoted reply.")
302
+
303
+ reply = TeamsActivity(type=ActivityTypes.message)
304
+ reply.add_quoted_reply(
305
+ message_id,
306
+ "This response includes a quote of your message.",
307
+ )
308
+ await context.send_activity(reply)
309
+ ```
310
+
311
+ Use `context.activity.get_quoted_messages()` to inspect quoted messages on an
312
+ incoming activity.
313
+
271
314
  ### Messaging Extensions
272
315
 
273
316
  ```python
@@ -0,0 +1 @@
1
+ 1.8.0.dev0
@@ -0,0 +1,132 @@
1
+ # Copyright (c) Microsoft Corporation. All rights reserved.
2
+ # Licensed under the MIT License.
3
+
4
+ """Construction and caching of the Teams :class:`ApiClient` for a turn.
5
+
6
+ The client is cached on turn context services so it is built at most once per turn, and
7
+ is configured with a token factory derived from the turn's identity when one is
8
+ available.
9
+ """
10
+
11
+ import ssl
12
+ import certifi
13
+
14
+ from typing import Callable, Awaitable
15
+
16
+ import httpx
17
+
18
+ from microsoft_teams.common import Client, ClientOptions
19
+ from microsoft_teams.api import ApiClient
20
+
21
+ from microsoft_agents.hosting.core import (
22
+ Connections,
23
+ TurnContext,
24
+ )
25
+
26
+ _ssl_context: ssl.SSLContext | None = None
27
+
28
+
29
+ def _get_ssl_context() -> ssl.SSLContext:
30
+ """Get or create the SSL context for verifying HTTPS requests.
31
+
32
+ httpx by default creates a new SSL context for each new client instance. This can be
33
+ inefficient. For example, at the time of writing this, without caching the SSL context,
34
+ the hosting_msteams unit tests took 32 seconds to complete. With caching, the total
35
+ time for the unit tests dropped to 7 seconds. For an agent with lots of traffic,
36
+ caching the SSL context may significantly improve performance and avoid extra io operations.
37
+ """
38
+ global _ssl_context
39
+
40
+ if _ssl_context is None:
41
+ _ssl_context = ssl.create_default_context(cafile=certifi.where())
42
+ return _ssl_context
43
+
44
+
45
+ def _client(
46
+ base_url: str,
47
+ headers: dict,
48
+ token_factory: Callable[[], Awaitable[str]] | None = None,
49
+ ) -> Client:
50
+ """
51
+ Create a new Client instance configured with the given base URL, headers, and token factory.
52
+
53
+ :param base_url: The base URL for the client.
54
+ :param headers: The headers to include in the client requests.
55
+ :param token_factory: A callable that returns an access token asynchronously, or None if no token is required.
56
+ :return: A configured Client instance.
57
+ """
58
+
59
+ options = ClientOptions(
60
+ base_url=base_url,
61
+ headers=headers,
62
+ token=token_factory,
63
+ )
64
+
65
+ return Client(
66
+ options,
67
+ _http=httpx.AsyncClient(
68
+ base_url=base_url,
69
+ headers=headers,
70
+ timeout=options.timeout,
71
+ verify=_get_ssl_context(),
72
+ ),
73
+ )
74
+
75
+
76
+ def _get_teams_api_client(context: TurnContext) -> ApiClient:
77
+ """
78
+ Get the cached Teams API client from the context.
79
+
80
+ :param context: The turn context.
81
+ :return: The cached Teams API client.
82
+ :raises ValueError: If the Teams API client is not found.
83
+ """
84
+ api_client = context.services.get(ApiClient)
85
+ if isinstance(api_client, ApiClient):
86
+ return api_client
87
+ raise ValueError("Unable to retrieve Teams API client.")
88
+
89
+
90
+ def _set_teams_api_client(
91
+ context: TurnContext, connection_manager: Connections
92
+ ) -> None:
93
+ """
94
+ Set the Teams API client in the context if it is not already set.
95
+
96
+ :param context: The turn context.
97
+ :param connection_manager: The connection manager.
98
+ """
99
+
100
+ if context.services.has(ApiClient):
101
+ return
102
+
103
+ headers = {
104
+ "Accept": "application/json",
105
+ "Content-Type": "application/json",
106
+ }
107
+
108
+ token_factory: Callable[[], Awaitable[str]] | None = None
109
+
110
+ if context.identity:
111
+ provider = connection_manager.get_token_provider(
112
+ context.identity, context.activity.service_url
113
+ )
114
+
115
+ async def _token_factory() -> str:
116
+ return await provider.get_access_token(
117
+ "https://api.botframework.com",
118
+ ["https://api.botframework.com/.default"],
119
+ )
120
+
121
+ token_factory = _token_factory
122
+
123
+ api_client = ApiClient(
124
+ context.activity.service_url,
125
+ options=_client(
126
+ base_url=context.activity.service_url,
127
+ headers=headers,
128
+ token_factory=token_factory,
129
+ ),
130
+ )
131
+
132
+ context.services.set(ApiClient, api_client)
@@ -14,10 +14,12 @@ from microsoft_teams.api.models.channel_data import ChannelData, ChannelInfo, Te
14
14
  from microsoft_agents.activity import (
15
15
  Activity,
16
16
  ActivityTypes,
17
+ Entity,
17
18
  InvokeResponse,
18
19
  )
19
20
  from microsoft_agents.hosting.core import TurnContext
20
21
 
22
+ from .models import TargetedMessageInfo
21
23
  from .type_defs import CommandSelector
22
24
 
23
25
 
@@ -146,3 +148,41 @@ async def _send_invoke_response(context: TurnContext, body: Any = None) -> None:
146
148
  value=InvokeResponse(status=int(HTTPStatus.OK), body=serialized_body),
147
149
  )
148
150
  )
151
+
152
+
153
+ def _apply_prompt_preview_normalizer(activity: Activity, message_id: str) -> None:
154
+ """Normalize the prompt preview for the given activity.
155
+
156
+ :param activity: The activity to normalize.
157
+ :param message_id: The message ID to associate with the targeted message info.
158
+ :return: None
159
+ """
160
+ pattern = re.compile(r"<quoted messageId=\"[^\"]*\"/>")
161
+ target_type = "quotedreply"
162
+ if activity.entities:
163
+
164
+ def _keep(entity: Entity) -> bool:
165
+ return entity.type.lower() != target_type
166
+
167
+ activity.entities[:] = [entity for entity in activity.entities if _keep(entity)]
168
+
169
+ if activity.text:
170
+ text_without_placeholder = pattern.sub("", activity.text)
171
+ if len(text_without_placeholder) != len(activity.text):
172
+ activity.text = text_without_placeholder.strip()
173
+
174
+ if not activity._get_entities_by_type("targetedmessageinfo"):
175
+ activity.entities = activity.entities or []
176
+ activity.entities.append(TargetedMessageInfo(message_id=message_id))
177
+
178
+
179
+ def _is_recipient_targeted(activity: Activity) -> bool:
180
+ """Check if the recipient is targeted in the given activity.
181
+
182
+ :param activity: The activity to check.
183
+ :return: True if the recipient is targeted, False otherwise.
184
+ """
185
+ recipient = activity.recipient
186
+ if recipient is None:
187
+ return False
188
+ return recipient and getattr(recipient, "isTargeted", None) is True
@@ -0,0 +1,7 @@
1
+ # Copyright (c) Microsoft Corporation. All rights reserved.
2
+ # Licensed under the MIT License.
3
+
4
+ from .quoted_reply import QuotedReply, QuotedReplyData
5
+ from .targeted_message_info import TargetedMessageInfo
6
+
7
+ __all__ = ["QuotedReply", "QuotedReplyData", "TargetedMessageInfo"]
@@ -0,0 +1,24 @@
1
+ # Copyright (c) Microsoft Corporation. All rights reserved.
2
+ # Licensed under the MIT License.
3
+
4
+ from typing import Literal
5
+ from microsoft_agents.activity import AgentsModel, Entity
6
+
7
+
8
+ class QuotedReplyData(AgentsModel):
9
+ """Represents a quoted reply in a Teams activity."""
10
+
11
+ message_id: str
12
+ sender_id: str | None = None
13
+ sender_name: str | None = None
14
+ preview: str | None = None
15
+ time: str | None = None
16
+ is_reply_deleted: bool | None = None
17
+ validated_message_reference: bool | None = None
18
+
19
+
20
+ class QuotedReply(Entity):
21
+ """Contains the metadata for a quoted Teams message."""
22
+
23
+ type: Literal["quotedReply"] = "quotedReply"
24
+ quoted_reply: QuotedReplyData | None = None
@@ -0,0 +1,12 @@
1
+ # Copyright (c) Microsoft Corporation. All rights reserved.
2
+ # Licensed under the MIT License.
3
+
4
+ from typing import Literal
5
+ from microsoft_agents.activity import Entity
6
+
7
+
8
+ class TargetedMessageInfo(Entity):
9
+ """Identifies the inbound targeted message associated with a Prompt Preview response."""
10
+
11
+ type: Literal["targetedMessageInfo"] = "targetedMessageInfo"
12
+ message_id: str
@@ -3,7 +3,10 @@
3
3
 
4
4
  """Teams-aware :class:`Activity` subclass exposing Teams channel data helpers."""
5
5
 
6
+ import html
7
+
6
8
  from typing import Literal
9
+ from typing_extensions import Self
7
10
 
8
11
  from microsoft_teams.api.models import (
9
12
  ChannelData,
@@ -15,7 +18,8 @@ from microsoft_teams.api.models import (
15
18
 
16
19
  from microsoft_agents.activity import Activity
17
20
 
18
- from ._utils import _try_get_channel_data
21
+ from .models import QuotedReply, QuotedReplyData, TargetedMessageInfo
22
+ from ._utils import _try_get_channel_data, _is_recipient_targeted
19
23
 
20
24
 
21
25
  class TeamsActivity(Activity):
@@ -121,3 +125,53 @@ class TeamsActivity(Activity):
121
125
  feedback_loop=FeedbackLoop(type=feedback_loop_type)
122
126
  )
123
127
  return True
128
+
129
+ def is_recipient_targeted(self) -> bool:
130
+ """Check if the recipient is targeted in the activity.
131
+
132
+ :return: True if the recipient is targeted, False otherwise.
133
+ """
134
+ return _is_recipient_targeted(self)
135
+
136
+ def get_targeted_message_info(self) -> TargetedMessageInfo | None:
137
+ """Get the targeted message information from the activity.
138
+
139
+ :return: The targeted message information if available, None otherwise.
140
+ """
141
+ res = self._get_entity_by_type("targetedMessageInfo")
142
+ if res:
143
+ return self._convert_entity(res, entity_cls=TargetedMessageInfo)
144
+ return None
145
+
146
+ def get_quoted_messages(self) -> list[QuotedReply]:
147
+ """Get the quoted messages from the activity.
148
+
149
+ :return: A list of quoted messages.
150
+ """
151
+ return self._convert_entity_list(
152
+ self._get_entities_by_type("quotedReply"), entity_cls=QuotedReply
153
+ )
154
+
155
+ def add_quoted_reply(self, message_id: str, text: str | None = None) -> Self:
156
+ """Add a quoted reply to the activity.
157
+
158
+ :param message_id: The ID of the message being quoted.
159
+ :param text: The text of the quoted reply.
160
+ :return: The updated activity instance.
161
+ """
162
+ self.entities = self.entities or []
163
+ self.entities.append(
164
+ QuotedReply(
165
+ type="quotedReply",
166
+ quoted_reply=QuotedReplyData(
167
+ message_id=message_id,
168
+ ),
169
+ )
170
+ )
171
+
172
+ placeholder = f'<quoted messageId="{html.escape(message_id, quote=True)}"/>'
173
+ self.text = f"{self.text or ''}{placeholder}"
174
+ if text is not None:
175
+ self.text = f"{self.text} {text}"
176
+
177
+ return self
@@ -123,9 +123,17 @@ class TeamsAgentExtension(Generic[StateT]):
123
123
  def _configure_app(self):
124
124
  """Configure the underlying AgentApplication with Teams-specific routes."""
125
125
 
126
+ async def on_close_turn(context: TurnContext) -> None:
127
+ if context.activity.channel_id == Channels.ms_teams:
128
+ api_client = context.services.get(ApiClient)
129
+ if api_client is not None:
130
+ await api_client.http.http.aclose()
131
+
126
132
  async def on_before_turn(context: TurnContext, state: StateT) -> bool:
127
133
  if context.activity.channel_id == Channels.ms_teams:
128
134
  _set_teams_api_client(context, self._app.connection_manager)
135
+ context._on_aclose(on_close_turn)
136
+
129
137
  # caches the deserialized version of ChannelData
130
138
  context.activity.channel_data = _try_get_channel_data(context.activity)
131
139
  return True
@@ -5,6 +5,7 @@
5
5
 
6
6
  from __future__ import annotations
7
7
 
8
+ import warnings
8
9
  from typing import cast
9
10
 
10
11
  from msgraph import GraphServiceClient
@@ -13,8 +14,9 @@ from microsoft_teams.api import ApiClient
13
14
 
14
15
  from microsoft_agents.activity import (
15
16
  Activity,
16
- ActivityTreatment,
17
- ActivityTreatmentTypes,
17
+ ActivityTypes,
18
+ ChannelAccount,
19
+ InputHints,
18
20
  ResourceResponse,
19
21
  )
20
22
  from microsoft_agents.hosting.core import (
@@ -30,6 +32,7 @@ from ._graph import (
30
32
  )
31
33
  from ._teams_api_client import _get_teams_api_client, _set_teams_api_client
32
34
  from .teams_activity import TeamsActivity
35
+ from ._utils import _apply_prompt_preview_normalizer, _is_recipient_targeted
33
36
 
34
37
 
35
38
  class TeamsTurnContext(TurnContext):
@@ -98,42 +101,92 @@ class TeamsTurnContext(TurnContext):
98
101
  """Get the API client for the Teams turn context."""
99
102
  return _get_teams_api_client(self)
100
103
 
101
- @staticmethod
102
- def _make_targeted_activity(activity: Activity) -> None:
104
+ def _apply_prompt_preview(self, activity: Activity) -> None:
103
105
  """
104
- Make an activity targeted.
106
+ Apply the prompt preview to the given activity.
105
107
 
106
- :param activity: The activity to make targeted.
108
+ :param activity: The activity to apply the prompt preview to.
107
109
  :return: None
108
110
  """
109
- activity.entities = activity.entities or []
110
- activity.entities.append(
111
- ActivityTreatment(treatment=ActivityTreatmentTypes.TARGETED)
111
+ if (
112
+ activity.type == ActivityTypes.message
113
+ and _is_recipient_targeted(self.activity)
114
+ and self.activity.id
115
+ ):
116
+ _apply_prompt_preview_normalizer(activity, self.activity.id)
117
+
118
+ async def send_activity(
119
+ self,
120
+ activity_or_text: Activity | str,
121
+ speak: str | None = None,
122
+ input_hint: str | None = None,
123
+ ) -> ResourceResponse:
124
+ """Send an activity after applying the prompt preview.
125
+
126
+ :param activity_or_text: The activity or text to send.
127
+ :param speak: Optional speech text for the activity.
128
+ :param input_hint: Optional input hint for the activity.
129
+ :return: The resource response for the sent activity.
130
+ """
131
+
132
+ if isinstance(activity_or_text, str):
133
+ activity_or_text = Activity(
134
+ type=ActivityTypes.message,
135
+ text=activity_or_text,
136
+ input_hint=input_hint or InputHints.accepting_input,
137
+ )
138
+ if speak:
139
+ activity_or_text.speak = speak
140
+
141
+ self._apply_prompt_preview(activity_or_text)
142
+ return await TurnContext.send_activity(self._original, activity_or_text)
143
+
144
+ async def send_activities(
145
+ self, activities: list[Activity]
146
+ ) -> list[ResourceResponse]:
147
+ """Send multiple activities after applying the prompt preview to each.
148
+
149
+ :param activities: A list of activities to send.
150
+ :return: A list of resource responses for the sent activities.
151
+ """
152
+
153
+ for activity in activities:
154
+ self._apply_prompt_preview(activity)
155
+
156
+ return await TurnContext.send_activities(
157
+ self._original,
158
+ activities,
112
159
  )
113
160
 
114
- async def send_targeted_activity(self, activity: Activity) -> ResourceResponse:
161
+ async def send_targeted_activity(
162
+ self,
163
+ activity: str | Activity,
164
+ recipient: str | ChannelAccount | None = None,
165
+ ) -> ResourceResponse:
115
166
  """
116
167
  Send a targeted activity.
117
168
 
118
169
  :param activity: The activity to send.
170
+ :param recipient: The recipient to target the activity to. Can be a string or a ChannelAccount instance.
119
171
  :return: The resource response.
120
172
  """
121
- TeamsTurnContext._make_targeted_activity(activity)
173
+ if recipient is None:
174
+ warnings.warn(
175
+ "Using an empty recipient is deprecated and will be removed in a future release.",
176
+ DeprecationWarning,
177
+ stacklevel=2,
178
+ )
179
+ if isinstance(activity, str) or not activity.recipient:
180
+ raise ValueError(
181
+ "Cannot infer the recipient from the passed-in activity."
182
+ )
183
+ recipient = activity.recipient
184
+
185
+ if isinstance(activity, str):
186
+ activity = Activity(type=ActivityTypes.message, text=activity)
187
+ activity.with_targeted_recipient(recipient)
122
188
  return await self.send_activity(activity)
123
189
 
124
- async def send_targeted_activities(
125
- self, activities: list[Activity]
126
- ) -> list[ResourceResponse]:
127
- """
128
- Send a list of targeted activities.
129
-
130
- :param activities: The list of activities to send.
131
- :return: A list of resource responses.
132
- """
133
- for activity in activities:
134
- TeamsTurnContext._make_targeted_activity(activity)
135
- return await self.send_activities(activities)
136
-
137
190
  def get_graph_client(
138
191
  self,
139
192
  handler_name: str | None = None,
@@ -1,3 +1,26 @@
1
+ Metadata-Version: 2.4
2
+ Name: microsoft-agents-hosting-msteams
3
+ Version: 1.8.0.dev0
4
+ Summary: Integration library for Microsoft Agents with Teams
5
+ Author: Microsoft Corporation
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/microsoft/Agents
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Programming Language :: Python :: 3.14
13
+ Classifier: Operating System :: OS Independent
14
+ Requires-Python: >=3.11
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: microsoft-agents-hosting-core==1.8.0.dev0
18
+ Requires-Dist: aiohttp>=3.11.11
19
+ Requires-Dist: microsoft-teams-api==2.1.0
20
+ Requires-Dist: msgraph-sdk>=1.58.0
21
+ Dynamic: license-file
22
+ Dynamic: requires-dist
23
+
1
24
  # microsoft-agents-hosting-msteams
2
25
 
3
26
  [![PyPI version](https://img.shields.io/pypi/v/microsoft-agents-hosting-msteams)](https://pypi.org/project/microsoft-agents-hosting-msteams/)
@@ -18,6 +41,15 @@ This library is part of the **Microsoft 365 Agents SDK for Python** — a compre
18
41
  <th style="width:20%">Date</th>
19
42
  <th style="width:60%">Release Notes</th>
20
43
  </tr>
44
+ <tr>
45
+ <td>1.8.0</td>
46
+ <td>2026-10-01</td>
47
+ <td>
48
+ <a href="https://github.com/microsoft/Agents-for-python/blob/main/changelog.md#microsoft-365-agents-sdk-for-python---release-notes-v180">
49
+ 1.8.0 Release Notes
50
+ </a>
51
+ </td>
52
+ </tr>
21
53
  <tr>
22
54
  <td>1.7.0</td>
23
55
  <td>2026-09-17</td>
@@ -230,6 +262,10 @@ context.activity.notify_user(alert_in_meeting=True)
230
262
 
231
263
  # Attach a feedback loop to a message being sent (Copilot scenarios)
232
264
  context.activity.enable_feedback_loop()
265
+
266
+ # Inspect quoted messages or targeted-message metadata
267
+ quoted_messages = context.activity.get_quoted_messages()
268
+ targeted_message = context.activity.get_targeted_message_info()
233
269
  ```
234
270
 
235
271
  #### `context.api_client` → `ApiClient`
@@ -238,13 +274,43 @@ Direct access to the Teams REST API client, pre-authenticated for the current tu
238
274
 
239
275
  #### Sending targeted activities
240
276
 
241
- `TeamsTurnContext` adds two methods for sending activities that target a specific user in a meeting:
277
+ Use `TeamsTurnContext.send_targeted_activity()` to send a private activity to a
278
+ specific user in a meeting or group conversation. Pass either the recipient ID
279
+ or a `ChannelAccount`:
280
+
281
+ ```python
282
+ await context.send_targeted_activity(
283
+ "Only you can see this message.",
284
+ recipient.id,
285
+ )
286
+ ```
287
+
288
+ Replies to inbound targeted activities automatically include the prompt-preview
289
+ metadata required by Teams.
290
+
291
+ #### Sending quoted replies
292
+
293
+ Use `TeamsActivity.add_quoted_reply()` to quote an existing message:
242
294
 
243
295
  ```python
244
- await context.send_targeted_activity(activity)
245
- await context.send_targeted_activities([activity1, activity2])
296
+ from microsoft_agents.activity import ActivityTypes
297
+ from microsoft_agents.hosting.msteams import TeamsActivity
298
+
299
+ message_id = context.activity.id
300
+ if not message_id:
301
+ raise ValueError("Message ID is required for a quoted reply.")
302
+
303
+ reply = TeamsActivity(type=ActivityTypes.message)
304
+ reply.add_quoted_reply(
305
+ message_id,
306
+ "This response includes a quote of your message.",
307
+ )
308
+ await context.send_activity(reply)
246
309
  ```
247
310
 
311
+ Use `context.activity.get_quoted_messages()` to inspect quoted messages on an
312
+ incoming activity.
313
+
248
314
  ### Messaging Extensions
249
315
 
250
316
  ```python
@@ -35,6 +35,9 @@ microsoft_agents/hosting/msteams/message/route_handlers.py
35
35
  microsoft_agents/hosting/msteams/message_extension/__init__.py
36
36
  microsoft_agents/hosting/msteams/message_extension/message_extension.py
37
37
  microsoft_agents/hosting/msteams/message_extension/route_handlers.py
38
+ microsoft_agents/hosting/msteams/models/__init__.py
39
+ microsoft_agents/hosting/msteams/models/quoted_reply.py
40
+ microsoft_agents/hosting/msteams/models/targeted_message_info.py
38
41
  microsoft_agents/hosting/msteams/task_module/__init__.py
39
42
  microsoft_agents/hosting/msteams/task_module/route_handlers.py
40
43
  microsoft_agents/hosting/msteams/task_module/task_module.py
@@ -1,4 +1,4 @@
1
- microsoft-agents-hosting-core==1.7.0.dev9
1
+ microsoft-agents-hosting-core==1.8.0.dev0
2
2
  aiohttp>=3.11.11
3
3
  microsoft-teams-api==2.1.0
4
4
  msgraph-sdk>=1.58.0
@@ -1,26 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: microsoft-agents-hosting-msteams
3
- Version: 1.7.0.dev9
4
- Summary: Integration library for Microsoft Agents with Teams
5
- Author: Microsoft Corporation
6
- License-Expression: MIT
7
- Project-URL: Homepage, https://github.com/microsoft/Agents
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: Programming Language :: Python :: 3.11
10
- Classifier: Programming Language :: Python :: 3.12
11
- Classifier: Programming Language :: Python :: 3.13
12
- Classifier: Programming Language :: Python :: 3.14
13
- Classifier: Operating System :: OS Independent
14
- Requires-Python: >=3.11
15
- Description-Content-Type: text/markdown
16
- License-File: LICENSE
17
- Requires-Dist: microsoft-agents-hosting-core==1.7.0.dev9
18
- Requires-Dist: aiohttp>=3.11.11
19
- Requires-Dist: microsoft-teams-api==2.1.0
20
- Requires-Dist: msgraph-sdk>=1.58.0
21
- Dynamic: license-file
22
- Dynamic: requires-dist
23
-
24
1
  # microsoft-agents-hosting-msteams
25
2
 
26
3
  [![PyPI version](https://img.shields.io/pypi/v/microsoft-agents-hosting-msteams)](https://pypi.org/project/microsoft-agents-hosting-msteams/)
@@ -41,6 +18,15 @@ This library is part of the **Microsoft 365 Agents SDK for Python** — a compre
41
18
  <th style="width:20%">Date</th>
42
19
  <th style="width:60%">Release Notes</th>
43
20
  </tr>
21
+ <tr>
22
+ <td>1.8.0</td>
23
+ <td>2026-10-01</td>
24
+ <td>
25
+ <a href="https://github.com/microsoft/Agents-for-python/blob/main/changelog.md#microsoft-365-agents-sdk-for-python---release-notes-v180">
26
+ 1.8.0 Release Notes
27
+ </a>
28
+ </td>
29
+ </tr>
44
30
  <tr>
45
31
  <td>1.7.0</td>
46
32
  <td>2026-09-17</td>
@@ -253,6 +239,10 @@ context.activity.notify_user(alert_in_meeting=True)
253
239
 
254
240
  # Attach a feedback loop to a message being sent (Copilot scenarios)
255
241
  context.activity.enable_feedback_loop()
242
+
243
+ # Inspect quoted messages or targeted-message metadata
244
+ quoted_messages = context.activity.get_quoted_messages()
245
+ targeted_message = context.activity.get_targeted_message_info()
256
246
  ```
257
247
 
258
248
  #### `context.api_client` → `ApiClient`
@@ -261,13 +251,43 @@ Direct access to the Teams REST API client, pre-authenticated for the current tu
261
251
 
262
252
  #### Sending targeted activities
263
253
 
264
- `TeamsTurnContext` adds two methods for sending activities that target a specific user in a meeting:
254
+ Use `TeamsTurnContext.send_targeted_activity()` to send a private activity to a
255
+ specific user in a meeting or group conversation. Pass either the recipient ID
256
+ or a `ChannelAccount`:
257
+
258
+ ```python
259
+ await context.send_targeted_activity(
260
+ "Only you can see this message.",
261
+ recipient.id,
262
+ )
263
+ ```
264
+
265
+ Replies to inbound targeted activities automatically include the prompt-preview
266
+ metadata required by Teams.
267
+
268
+ #### Sending quoted replies
269
+
270
+ Use `TeamsActivity.add_quoted_reply()` to quote an existing message:
265
271
 
266
272
  ```python
267
- await context.send_targeted_activity(activity)
268
- await context.send_targeted_activities([activity1, activity2])
273
+ from microsoft_agents.activity import ActivityTypes
274
+ from microsoft_agents.hosting.msteams import TeamsActivity
275
+
276
+ message_id = context.activity.id
277
+ if not message_id:
278
+ raise ValueError("Message ID is required for a quoted reply.")
279
+
280
+ reply = TeamsActivity(type=ActivityTypes.message)
281
+ reply.add_quoted_reply(
282
+ message_id,
283
+ "This response includes a quote of your message.",
284
+ )
285
+ await context.send_activity(reply)
269
286
  ```
270
287
 
288
+ Use `context.activity.get_quoted_messages()` to inspect quoted messages on an
289
+ incoming activity.
290
+
271
291
  ### Messaging Extensions
272
292
 
273
293
  ```python
@@ -1 +0,0 @@
1
- 1.7.0.dev9
@@ -1,76 +0,0 @@
1
- # Copyright (c) Microsoft Corporation. All rights reserved.
2
- # Licensed under the MIT License.
3
-
4
- """Construction and caching of the Teams :class:`ApiClient` for a turn.
5
-
6
- The client is cached on turn context services so it is built at most once per turn, and
7
- is configured with a token factory derived from the turn's identity when one is
8
- available.
9
- """
10
-
11
- from microsoft_teams.common import ClientOptions
12
- from microsoft_teams.api import ApiClient
13
-
14
- from microsoft_agents.hosting.core import (
15
- Connections,
16
- TurnContext,
17
- )
18
-
19
-
20
- def _get_teams_api_client(context: TurnContext) -> ApiClient:
21
- """
22
- Get the cached Teams API client from the context.
23
-
24
- :param context: The turn context.
25
- :return: The cached Teams API client.
26
- :raises ValueError: If the Teams API client is not found.
27
- """
28
- api_client = context.services.get(ApiClient)
29
- if isinstance(api_client, ApiClient):
30
- return api_client
31
- raise ValueError("Unable to retrieve Teams API client.")
32
-
33
-
34
- def _set_teams_api_client(
35
- context: TurnContext, connection_manager: Connections
36
- ) -> None:
37
- """
38
- Set the Teams API client in the context if it is not already set.
39
-
40
- :param context: The turn context.
41
- :param connection_manager: The connection manager.
42
- """
43
-
44
- if context.services.has(ApiClient):
45
- return
46
-
47
- headers = {
48
- "Accept": "application/json",
49
- "Content-Type": "application/json",
50
- }
51
-
52
- options: ClientOptions
53
-
54
- if context.identity:
55
- provider = connection_manager.get_token_provider(
56
- context.identity, context.activity.service_url
57
- )
58
-
59
- async def token_factory() -> str:
60
- return await provider.get_access_token(
61
- "https://api.botframework.com",
62
- ["https://api.botframework.com/.default"],
63
- )
64
-
65
- options = ClientOptions(
66
- base_url=context.activity.service_url, headers=headers, token=token_factory
67
- )
68
- else:
69
- options = ClientOptions(base_url=context.activity.service_url, headers=headers)
70
-
71
- api_client = ApiClient(
72
- context.activity.service_url,
73
- options,
74
- )
75
-
76
- context.services.set(ApiClient, api_client)