microsoft-agents-a365-notifications 0.2.1.dev13__tar.gz → 0.2.1.dev19__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.
- {microsoft_agents_a365_notifications-0.2.1.dev13 → microsoft_agents_a365_notifications-0.2.1.dev19}/PKG-INFO +1 -1
- microsoft_agents_a365_notifications-0.2.1.dev19/microsoft_agents_a365/notifications/agent_notification.py +457 -0
- {microsoft_agents_a365_notifications-0.2.1.dev13 → microsoft_agents_a365_notifications-0.2.1.dev19}/microsoft_agents_a365/notifications/models/__init__.py +10 -3
- microsoft_agents_a365_notifications-0.2.1.dev19/microsoft_agents_a365/notifications/models/agent_lifecycle_event.py +22 -0
- microsoft_agents_a365_notifications-0.2.1.dev19/microsoft_agents_a365/notifications/models/agent_notification_activity.py +176 -0
- microsoft_agents_a365_notifications-0.2.1.dev19/microsoft_agents_a365/notifications/models/agent_subchannel.py +26 -0
- microsoft_agents_a365_notifications-0.2.1.dev19/microsoft_agents_a365/notifications/models/email_reference.py +25 -0
- microsoft_agents_a365_notifications-0.2.1.dev19/microsoft_agents_a365/notifications/models/email_response.py +51 -0
- microsoft_agents_a365_notifications-0.2.1.dev19/microsoft_agents_a365/notifications/models/notification_types.py +22 -0
- microsoft_agents_a365_notifications-0.2.1.dev19/microsoft_agents_a365/notifications/models/wpx_comment.py +29 -0
- {microsoft_agents_a365_notifications-0.2.1.dev13 → microsoft_agents_a365_notifications-0.2.1.dev19}/microsoft_agents_a365_notifications.egg-info/PKG-INFO +1 -1
- {microsoft_agents_a365_notifications-0.2.1.dev13 → microsoft_agents_a365_notifications-0.2.1.dev19}/setup.py +2 -1
- microsoft_agents_a365_notifications-0.2.1.dev13/microsoft_agents_a365/notifications/agent_notification.py +0 -186
- microsoft_agents_a365_notifications-0.2.1.dev13/microsoft_agents_a365/notifications/models/agent_lifecycle_event.py +0 -10
- microsoft_agents_a365_notifications-0.2.1.dev13/microsoft_agents_a365/notifications/models/agent_notification_activity.py +0 -88
- microsoft_agents_a365_notifications-0.2.1.dev13/microsoft_agents_a365/notifications/models/agent_subchannel.py +0 -12
- microsoft_agents_a365_notifications-0.2.1.dev13/microsoft_agents_a365/notifications/models/email_reference.py +0 -13
- microsoft_agents_a365_notifications-0.2.1.dev13/microsoft_agents_a365/notifications/models/email_response.py +0 -28
- microsoft_agents_a365_notifications-0.2.1.dev13/microsoft_agents_a365/notifications/models/notification_types.py +0 -10
- microsoft_agents_a365_notifications-0.2.1.dev13/microsoft_agents_a365/notifications/models/wpx_comment.py +0 -15
- {microsoft_agents_a365_notifications-0.2.1.dev13 → microsoft_agents_a365_notifications-0.2.1.dev19}/README.md +0 -0
- {microsoft_agents_a365_notifications-0.2.1.dev13 → microsoft_agents_a365_notifications-0.2.1.dev19}/microsoft_agents_a365/notifications/__init__.py +4 -4
- {microsoft_agents_a365_notifications-0.2.1.dev13 → microsoft_agents_a365_notifications-0.2.1.dev19}/microsoft_agents_a365_notifications.egg-info/SOURCES.txt +0 -0
- {microsoft_agents_a365_notifications-0.2.1.dev13 → microsoft_agents_a365_notifications-0.2.1.dev19}/microsoft_agents_a365_notifications.egg-info/dependency_links.txt +0 -0
- {microsoft_agents_a365_notifications-0.2.1.dev13 → microsoft_agents_a365_notifications-0.2.1.dev19}/microsoft_agents_a365_notifications.egg-info/requires.txt +0 -0
- {microsoft_agents_a365_notifications-0.2.1.dev13 → microsoft_agents_a365_notifications-0.2.1.dev19}/microsoft_agents_a365_notifications.egg-info/top_level.txt +0 -0
- {microsoft_agents_a365_notifications-0.2.1.dev13 → microsoft_agents_a365_notifications-0.2.1.dev19}/pyproject.toml +0 -0
- {microsoft_agents_a365_notifications-0.2.1.dev13 → microsoft_agents_a365_notifications-0.2.1.dev19}/setup.cfg +0 -0
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from collections.abc import Awaitable, Callable, Iterable
|
|
7
|
+
from typing import Any, TypeVar
|
|
8
|
+
|
|
9
|
+
from microsoft_agents.activity import ChannelId
|
|
10
|
+
from microsoft_agents.hosting.core import TurnContext
|
|
11
|
+
from microsoft_agents.hosting.core.app.state import TurnState
|
|
12
|
+
|
|
13
|
+
from .models.agent_lifecycle_event import AgentLifecycleEvent
|
|
14
|
+
from .models.agent_notification_activity import AgentNotificationActivity, NotificationTypes
|
|
15
|
+
from .models.agent_subchannel import AgentSubChannel
|
|
16
|
+
|
|
17
|
+
TContext = TypeVar("TContext", bound=TurnContext)
|
|
18
|
+
TState = TypeVar("TState", bound=TurnState)
|
|
19
|
+
|
|
20
|
+
#: Type alias for agent notification handler functions.
|
|
21
|
+
#:
|
|
22
|
+
#: Agent handlers are async functions that process notifications from Microsoft 365
|
|
23
|
+
#: applications. They receive the turn context, application state, and a typed
|
|
24
|
+
#: notification activity wrapper.
|
|
25
|
+
#:
|
|
26
|
+
#: Args:
|
|
27
|
+
#: context: The turn context for the current conversation turn.
|
|
28
|
+
#: state: The application state for the current turn.
|
|
29
|
+
#: notification: The typed notification activity with parsed entities.
|
|
30
|
+
#:
|
|
31
|
+
#: Example:
|
|
32
|
+
#: ```python
|
|
33
|
+
#: async def handle_email(
|
|
34
|
+
#: context: TurnContext,
|
|
35
|
+
#: state: TurnState,
|
|
36
|
+
#: notification: AgentNotificationActivity
|
|
37
|
+
#: ) -> None:
|
|
38
|
+
#: email = notification.email
|
|
39
|
+
#: if email:
|
|
40
|
+
#: print(f"Processing email: {email.id}")
|
|
41
|
+
#: ```
|
|
42
|
+
AgentHandler = Callable[[TContext, TState, AgentNotificationActivity], Awaitable[None]]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class AgentNotification:
|
|
46
|
+
"""Handler for agent notifications from Microsoft 365 applications.
|
|
47
|
+
|
|
48
|
+
This class provides decorators for registering handlers that respond to notifications
|
|
49
|
+
from various Microsoft 365 channels and subchannels. It supports routing based on
|
|
50
|
+
channel ID, subchannel, and lifecycle events.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
app: The application instance that will handle the routed notifications.
|
|
54
|
+
known_subchannels: Optional iterable of recognized subchannels. If None,
|
|
55
|
+
defaults to all values in the AgentSubChannel enum.
|
|
56
|
+
known_lifecycle_events: Optional iterable of recognized lifecycle events. If None,
|
|
57
|
+
defaults to all values in the AgentLifecycleEvent enum.
|
|
58
|
+
|
|
59
|
+
Example:
|
|
60
|
+
```python
|
|
61
|
+
from microsoft_agents.hosting import Application
|
|
62
|
+
from microsoft_agents_a365.notifications import AgentNotification
|
|
63
|
+
|
|
64
|
+
app = Application()
|
|
65
|
+
notifications = AgentNotification(app)
|
|
66
|
+
|
|
67
|
+
@notifications.on_email()
|
|
68
|
+
async def handle_email(context, state, notification):
|
|
69
|
+
email = notification.email
|
|
70
|
+
if email:
|
|
71
|
+
await context.send_activity(f"Received email: {email.id}")
|
|
72
|
+
```
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
def __init__(
|
|
76
|
+
self,
|
|
77
|
+
app: Any,
|
|
78
|
+
known_subchannels: Iterable[str | AgentSubChannel] | None = None,
|
|
79
|
+
known_lifecycle_events: Iterable[str | AgentLifecycleEvent] | None = None,
|
|
80
|
+
):
|
|
81
|
+
self._app = app
|
|
82
|
+
if known_subchannels is None:
|
|
83
|
+
source_subchannels: Iterable[str | AgentSubChannel] = AgentSubChannel
|
|
84
|
+
else:
|
|
85
|
+
source_subchannels = known_subchannels
|
|
86
|
+
|
|
87
|
+
self._known_subchannels = {
|
|
88
|
+
normalized
|
|
89
|
+
for normalized in (
|
|
90
|
+
self._normalize_subchannel(sub_channel) for sub_channel in source_subchannels
|
|
91
|
+
)
|
|
92
|
+
if normalized
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if known_lifecycle_events is None:
|
|
96
|
+
source_lifecycle_events: Iterable[str | AgentLifecycleEvent] = AgentLifecycleEvent
|
|
97
|
+
else:
|
|
98
|
+
source_lifecycle_events = known_lifecycle_events
|
|
99
|
+
|
|
100
|
+
self._known_lifecycle_events = {
|
|
101
|
+
normalized
|
|
102
|
+
for normalized in (
|
|
103
|
+
self._normalize_lifecycleevent(lifecycle_event)
|
|
104
|
+
for lifecycle_event in source_lifecycle_events
|
|
105
|
+
)
|
|
106
|
+
if normalized
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
def on_agent_notification(
|
|
110
|
+
self,
|
|
111
|
+
channel_id: ChannelId,
|
|
112
|
+
**kwargs: Any,
|
|
113
|
+
):
|
|
114
|
+
"""Register a handler for notifications from a specific channel and subchannel.
|
|
115
|
+
|
|
116
|
+
This decorator registers a handler function to be called when a notification is
|
|
117
|
+
received from the specified channel and optional subchannel. The handler will
|
|
118
|
+
receive a typed AgentNotificationActivity wrapper.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
channel_id: The channel ID specifying the channel and optional subchannel
|
|
122
|
+
to listen for. Use "*" as the subchannel to match all subchannels.
|
|
123
|
+
**kwargs: Additional keyword arguments passed to the app's add_route method.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
A decorator function that registers the handler with the application.
|
|
127
|
+
|
|
128
|
+
Example:
|
|
129
|
+
```python
|
|
130
|
+
from microsoft_agents.activity import ChannelId
|
|
131
|
+
|
|
132
|
+
@notifications.on_agent_notification(
|
|
133
|
+
ChannelId(channel="agents", sub_channel="email")
|
|
134
|
+
)
|
|
135
|
+
async def handle_custom_channel(context, state, notification):
|
|
136
|
+
print(f"Received notification on {notification.channel}/{notification.sub_channel}")
|
|
137
|
+
```
|
|
138
|
+
"""
|
|
139
|
+
registered_channel = channel_id.channel.lower()
|
|
140
|
+
registered_subchannel = (channel_id.sub_channel or "*").lower()
|
|
141
|
+
|
|
142
|
+
def route_selector(context: TurnContext) -> bool:
|
|
143
|
+
ch = context.activity.channel_id
|
|
144
|
+
received_channel = (ch.channel if ch else "").lower()
|
|
145
|
+
received_subchannel = (ch.sub_channel if ch and ch.sub_channel else "").lower()
|
|
146
|
+
if received_channel != registered_channel:
|
|
147
|
+
return False
|
|
148
|
+
if registered_subchannel == "*":
|
|
149
|
+
return True
|
|
150
|
+
if registered_subchannel not in self._known_subchannels:
|
|
151
|
+
return False
|
|
152
|
+
return received_subchannel == registered_subchannel
|
|
153
|
+
|
|
154
|
+
def create_handler(handler: AgentHandler):
|
|
155
|
+
async def route_handler(context: TurnContext, state: TurnState):
|
|
156
|
+
ana = AgentNotificationActivity(context.activity)
|
|
157
|
+
await handler(context, state, ana)
|
|
158
|
+
|
|
159
|
+
return route_handler
|
|
160
|
+
|
|
161
|
+
def decorator(handler: AgentHandler):
|
|
162
|
+
route_handler = create_handler(handler)
|
|
163
|
+
self._app.add_route(route_selector, route_handler, **kwargs)
|
|
164
|
+
return route_handler
|
|
165
|
+
|
|
166
|
+
return decorator
|
|
167
|
+
|
|
168
|
+
def on_agent_lifecycle_notification(
|
|
169
|
+
self,
|
|
170
|
+
lifecycle_event: str,
|
|
171
|
+
**kwargs: Any,
|
|
172
|
+
):
|
|
173
|
+
"""Register a handler for agent lifecycle event notifications.
|
|
174
|
+
|
|
175
|
+
This decorator registers a handler function to be called when lifecycle events
|
|
176
|
+
occur, such as user creation, deletion, or workload onboarding updates.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
lifecycle_event: The lifecycle event to listen for. Use "*" to match all
|
|
180
|
+
lifecycle events, or specify a specific event from AgentLifecycleEvent.
|
|
181
|
+
**kwargs: Additional keyword arguments passed to the app's add_route method.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
A decorator function that registers the handler with the application.
|
|
185
|
+
|
|
186
|
+
Example:
|
|
187
|
+
```python
|
|
188
|
+
@notifications.on_agent_lifecycle_notification("agenticuseridentitycreated")
|
|
189
|
+
async def handle_user_created(context, state, notification):
|
|
190
|
+
print("New user created")
|
|
191
|
+
```
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
def route_selector(context: TurnContext) -> bool:
|
|
195
|
+
ch = context.activity.channel_id
|
|
196
|
+
received_channel = ch.channel if ch else ""
|
|
197
|
+
received_channel = received_channel.lower()
|
|
198
|
+
if received_channel != "agents":
|
|
199
|
+
return False
|
|
200
|
+
if context.activity.name != NotificationTypes.AGENT_LIFECYCLE:
|
|
201
|
+
return False
|
|
202
|
+
if lifecycle_event == "*":
|
|
203
|
+
return True
|
|
204
|
+
if context.activity.value_type not in self._known_lifecycle_events:
|
|
205
|
+
return False
|
|
206
|
+
return True
|
|
207
|
+
|
|
208
|
+
def create_handler(handler: AgentHandler):
|
|
209
|
+
async def route_handler(context: TurnContext, state: TurnState):
|
|
210
|
+
ana = AgentNotificationActivity(context.activity)
|
|
211
|
+
await handler(context, state, ana)
|
|
212
|
+
|
|
213
|
+
return route_handler
|
|
214
|
+
|
|
215
|
+
def decorator(handler: AgentHandler):
|
|
216
|
+
route_handler = create_handler(handler)
|
|
217
|
+
self._app.add_route(route_selector, route_handler, **kwargs)
|
|
218
|
+
return route_handler
|
|
219
|
+
|
|
220
|
+
return decorator
|
|
221
|
+
|
|
222
|
+
def on_email(
|
|
223
|
+
self, **kwargs: Any
|
|
224
|
+
) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
|
|
225
|
+
"""Register a handler for Outlook email notifications.
|
|
226
|
+
|
|
227
|
+
This is a convenience decorator that registers a handler for notifications
|
|
228
|
+
from the email subchannel.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
**kwargs: Additional keyword arguments passed to the app's add_route method.
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
A decorator function that registers the handler with the application.
|
|
235
|
+
|
|
236
|
+
Example:
|
|
237
|
+
```python
|
|
238
|
+
@notifications.on_email()
|
|
239
|
+
async def handle_email(context, state, notification):
|
|
240
|
+
email = notification.email
|
|
241
|
+
if email:
|
|
242
|
+
print(f"Received email: {email.id}")
|
|
243
|
+
# Send a response
|
|
244
|
+
response = EmailResponse.create_email_response_activity(
|
|
245
|
+
"<p>Thank you for your email.</p>"
|
|
246
|
+
)
|
|
247
|
+
await context.send_activity(response)
|
|
248
|
+
```
|
|
249
|
+
"""
|
|
250
|
+
return self.on_agent_notification(
|
|
251
|
+
ChannelId(channel="agents", sub_channel=AgentSubChannel.EMAIL), **kwargs
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
def on_word(
|
|
255
|
+
self, **kwargs: Any
|
|
256
|
+
) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
|
|
257
|
+
"""Register a handler for Microsoft Word comment notifications.
|
|
258
|
+
|
|
259
|
+
This is a convenience decorator that registers a handler for notifications
|
|
260
|
+
from the Word subchannel.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
**kwargs: Additional keyword arguments passed to the app's add_route method.
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
A decorator function that registers the handler with the application.
|
|
267
|
+
|
|
268
|
+
Example:
|
|
269
|
+
```python
|
|
270
|
+
@notifications.on_word()
|
|
271
|
+
async def handle_word_comment(context, state, notification):
|
|
272
|
+
comment = notification.wpx_comment
|
|
273
|
+
if comment:
|
|
274
|
+
print(f"Received Word comment: {comment.comment_id}")
|
|
275
|
+
```
|
|
276
|
+
"""
|
|
277
|
+
return self.on_agent_notification(
|
|
278
|
+
ChannelId(channel="agents", sub_channel=AgentSubChannel.WORD), **kwargs
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
def on_excel(
|
|
282
|
+
self, **kwargs: Any
|
|
283
|
+
) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
|
|
284
|
+
"""Register a handler for Microsoft Excel comment notifications.
|
|
285
|
+
|
|
286
|
+
This is a convenience decorator that registers a handler for notifications
|
|
287
|
+
from the Excel subchannel.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
**kwargs: Additional keyword arguments passed to the app's add_route method.
|
|
291
|
+
|
|
292
|
+
Returns:
|
|
293
|
+
A decorator function that registers the handler with the application.
|
|
294
|
+
|
|
295
|
+
Example:
|
|
296
|
+
```python
|
|
297
|
+
@notifications.on_excel()
|
|
298
|
+
async def handle_excel_comment(context, state, notification):
|
|
299
|
+
comment = notification.wpx_comment
|
|
300
|
+
if comment:
|
|
301
|
+
print(f"Received Excel comment: {comment.comment_id}")
|
|
302
|
+
```
|
|
303
|
+
"""
|
|
304
|
+
return self.on_agent_notification(
|
|
305
|
+
ChannelId(channel="agents", sub_channel=AgentSubChannel.EXCEL), **kwargs
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
def on_powerpoint(
|
|
309
|
+
self, **kwargs: Any
|
|
310
|
+
) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
|
|
311
|
+
"""Register a handler for Microsoft PowerPoint comment notifications.
|
|
312
|
+
|
|
313
|
+
This is a convenience decorator that registers a handler for notifications
|
|
314
|
+
from the PowerPoint subchannel.
|
|
315
|
+
|
|
316
|
+
Args:
|
|
317
|
+
**kwargs: Additional keyword arguments passed to the app's add_route method.
|
|
318
|
+
|
|
319
|
+
Returns:
|
|
320
|
+
A decorator function that registers the handler with the application.
|
|
321
|
+
|
|
322
|
+
Example:
|
|
323
|
+
```python
|
|
324
|
+
@notifications.on_powerpoint()
|
|
325
|
+
async def handle_powerpoint_comment(context, state, notification):
|
|
326
|
+
comment = notification.wpx_comment
|
|
327
|
+
if comment:
|
|
328
|
+
print(f"Received PowerPoint comment: {comment.comment_id}")
|
|
329
|
+
```
|
|
330
|
+
"""
|
|
331
|
+
return self.on_agent_notification(
|
|
332
|
+
ChannelId(channel="agents", sub_channel=AgentSubChannel.POWERPOINT), **kwargs
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
def on_lifecycle(
|
|
336
|
+
self, **kwargs: Any
|
|
337
|
+
) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
|
|
338
|
+
"""Register a handler for all agent lifecycle event notifications.
|
|
339
|
+
|
|
340
|
+
This is a convenience decorator that registers a handler for all lifecycle
|
|
341
|
+
events using the wildcard "*" matcher.
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
**kwargs: Additional keyword arguments passed to the app's add_route method.
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
A decorator function that registers the handler with the application.
|
|
348
|
+
|
|
349
|
+
Example:
|
|
350
|
+
```python
|
|
351
|
+
@notifications.on_lifecycle()
|
|
352
|
+
async def handle_any_lifecycle_event(context, state, notification):
|
|
353
|
+
print(f"Lifecycle event type: {notification.notification_type}")
|
|
354
|
+
```
|
|
355
|
+
"""
|
|
356
|
+
return self.on_lifecycle_notification("*", **kwargs)
|
|
357
|
+
|
|
358
|
+
def on_user_created(
|
|
359
|
+
self, **kwargs: Any
|
|
360
|
+
) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
|
|
361
|
+
"""Register a handler for user creation lifecycle events.
|
|
362
|
+
|
|
363
|
+
This is a convenience decorator that registers a handler specifically for
|
|
364
|
+
agentic user identity creation events.
|
|
365
|
+
|
|
366
|
+
Args:
|
|
367
|
+
**kwargs: Additional keyword arguments passed to the app's add_route method.
|
|
368
|
+
|
|
369
|
+
Returns:
|
|
370
|
+
A decorator function that registers the handler with the application.
|
|
371
|
+
|
|
372
|
+
Example:
|
|
373
|
+
```python
|
|
374
|
+
@notifications.on_user_created()
|
|
375
|
+
async def handle_user_created(context, state, notification):
|
|
376
|
+
print("New agentic user identity created")
|
|
377
|
+
```
|
|
378
|
+
"""
|
|
379
|
+
return self.on_lifecycle_notification(AgentLifecycleEvent.USERCREATED, **kwargs)
|
|
380
|
+
|
|
381
|
+
def on_user_workload_onboarding(
|
|
382
|
+
self, **kwargs: Any
|
|
383
|
+
) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
|
|
384
|
+
"""Register a handler for user workload onboarding update events.
|
|
385
|
+
|
|
386
|
+
This is a convenience decorator that registers a handler for events that occur
|
|
387
|
+
when a user's workload onboarding status is updated.
|
|
388
|
+
|
|
389
|
+
Args:
|
|
390
|
+
**kwargs: Additional keyword arguments passed to the app's add_route method.
|
|
391
|
+
|
|
392
|
+
Returns:
|
|
393
|
+
A decorator function that registers the handler with the application.
|
|
394
|
+
|
|
395
|
+
Example:
|
|
396
|
+
```python
|
|
397
|
+
@notifications.on_user_workload_onboarding()
|
|
398
|
+
async def handle_onboarding_update(context, state, notification):
|
|
399
|
+
print("User workload onboarding status updated")
|
|
400
|
+
```
|
|
401
|
+
"""
|
|
402
|
+
return self.on_lifecycle_notification(
|
|
403
|
+
AgentLifecycleEvent.USERWORKLOADONBOARDINGUPDATED, **kwargs
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
def on_user_deleted(
|
|
407
|
+
self, **kwargs: Any
|
|
408
|
+
) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
|
|
409
|
+
"""Register a handler for user deletion lifecycle events.
|
|
410
|
+
|
|
411
|
+
This is a convenience decorator that registers a handler specifically for
|
|
412
|
+
agentic user identity deletion events.
|
|
413
|
+
|
|
414
|
+
Args:
|
|
415
|
+
**kwargs: Additional keyword arguments passed to the app's add_route method.
|
|
416
|
+
|
|
417
|
+
Returns:
|
|
418
|
+
A decorator function that registers the handler with the application.
|
|
419
|
+
|
|
420
|
+
Example:
|
|
421
|
+
```python
|
|
422
|
+
@notifications.on_user_deleted()
|
|
423
|
+
async def handle_user_deleted(context, state, notification):
|
|
424
|
+
print("Agentic user identity deleted")
|
|
425
|
+
```
|
|
426
|
+
"""
|
|
427
|
+
return self.on_lifecycle_notification(AgentLifecycleEvent.USERDELETED, **kwargs)
|
|
428
|
+
|
|
429
|
+
@staticmethod
|
|
430
|
+
def _normalize_subchannel(value: str | AgentSubChannel | None) -> str:
|
|
431
|
+
"""Normalize a subchannel value to a lowercase string.
|
|
432
|
+
|
|
433
|
+
Args:
|
|
434
|
+
value: The subchannel value to normalize, either as an enum or string.
|
|
435
|
+
|
|
436
|
+
Returns:
|
|
437
|
+
The normalized lowercase subchannel string, or empty string if None.
|
|
438
|
+
"""
|
|
439
|
+
if value is None:
|
|
440
|
+
return ""
|
|
441
|
+
resolved = value.value if isinstance(value, AgentSubChannel) else str(value)
|
|
442
|
+
return resolved.lower().strip()
|
|
443
|
+
|
|
444
|
+
@staticmethod
|
|
445
|
+
def _normalize_lifecycleevent(value: str | AgentLifecycleEvent | None) -> str:
|
|
446
|
+
"""Normalize a lifecycle event value to a lowercase string.
|
|
447
|
+
|
|
448
|
+
Args:
|
|
449
|
+
value: The lifecycle event value to normalize, either as an enum or string.
|
|
450
|
+
|
|
451
|
+
Returns:
|
|
452
|
+
The normalized lowercase lifecycle event string, or empty string if None.
|
|
453
|
+
"""
|
|
454
|
+
if value is None:
|
|
455
|
+
return ""
|
|
456
|
+
resolved = value.value if isinstance(value, AgentLifecycleEvent) else str(value)
|
|
457
|
+
return resolved.lower().strip()
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
# Copyright (c) Microsoft Corporation.
|
|
2
2
|
# Licensed under the MIT License.
|
|
3
3
|
|
|
4
|
+
"""Models and data classes for agent notifications.
|
|
5
|
+
|
|
6
|
+
This module contains the data models and enums used to represent notifications
|
|
7
|
+
from Microsoft 365 applications, including email references, document comments,
|
|
8
|
+
and lifecycle events.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .agent_lifecycle_event import AgentLifecycleEvent
|
|
4
12
|
from .agent_notification_activity import AgentNotificationActivity
|
|
13
|
+
from .agent_subchannel import AgentSubChannel
|
|
5
14
|
from .email_reference import EmailReference
|
|
6
|
-
from .wpx_comment import WpxComment
|
|
7
15
|
from .email_response import EmailResponse
|
|
8
16
|
from .notification_types import NotificationTypes
|
|
9
|
-
from .
|
|
10
|
-
from .agent_lifecycle_event import AgentLifecycleEvent
|
|
17
|
+
from .wpx_comment import WpxComment
|
|
11
18
|
|
|
12
19
|
__all__ = [
|
|
13
20
|
"AgentNotificationActivity",
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from enum import Enum
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AgentLifecycleEvent(str, Enum):
|
|
8
|
+
"""Enumeration of agent lifecycle event types.
|
|
9
|
+
|
|
10
|
+
This enum defines the different lifecycle events that can occur for agentic user
|
|
11
|
+
identities in the Microsoft 365 ecosystem.
|
|
12
|
+
|
|
13
|
+
Attributes:
|
|
14
|
+
USERCREATED: Event triggered when a new agentic user identity is created.
|
|
15
|
+
USERWORKLOADONBOARDINGUPDATED: Event triggered when a user's workload
|
|
16
|
+
onboarding status is updated.
|
|
17
|
+
USERDELETED: Event triggered when an agentic user identity is deleted.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
USERCREATED = "agenticuseridentitycreated"
|
|
21
|
+
USERWORKLOADONBOARDINGUPDATED = "agenticuserworkloadonboardingupdated"
|
|
22
|
+
USERDELETED = "agenticuseridentitydeleted"
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from typing import Any, Optional, Type, TypeVar
|
|
5
|
+
from microsoft_agents.activity import Activity
|
|
6
|
+
from .notification_types import NotificationTypes
|
|
7
|
+
from .email_reference import EmailReference
|
|
8
|
+
from .wpx_comment import WpxComment
|
|
9
|
+
|
|
10
|
+
TModel = TypeVar("TModel")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AgentNotificationActivity:
|
|
14
|
+
"""Wrapper around an Activity object with typed notification entities.
|
|
15
|
+
|
|
16
|
+
This class provides convenient access to typed notification entities extracted from
|
|
17
|
+
an Activity's entities collection. It automatically parses and validates email
|
|
18
|
+
notifications, Word/PowerPoint/Excel comments, and lifecycle events at construction
|
|
19
|
+
time.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
activity: The Activity object to wrap. Must not be None.
|
|
23
|
+
|
|
24
|
+
Raises:
|
|
25
|
+
ValueError: If the activity parameter is None.
|
|
26
|
+
|
|
27
|
+
Attributes:
|
|
28
|
+
activity: The underlying Activity object.
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
```python
|
|
32
|
+
async def email_handler(context: TurnContext, state: TurnState, notification: AgentNotificationActivity):
|
|
33
|
+
email = notification.email
|
|
34
|
+
if email:
|
|
35
|
+
print(f"Received email: {email.id}")
|
|
36
|
+
print(f"Body: {email.html_body}")
|
|
37
|
+
```
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(self, activity: Activity):
|
|
41
|
+
if not activity:
|
|
42
|
+
raise ValueError("activity parameter is required and cannot be None")
|
|
43
|
+
self.activity = activity
|
|
44
|
+
self._email: Optional[EmailReference] = None
|
|
45
|
+
self._wpx_comment: Optional[WpxComment] = None
|
|
46
|
+
self._notification_type: Optional[NotificationTypes] = None
|
|
47
|
+
|
|
48
|
+
entities = self.activity.entities or []
|
|
49
|
+
for ent in entities:
|
|
50
|
+
etype = ent.type.lower()
|
|
51
|
+
payload = getattr(ent, "additional_properties", ent)
|
|
52
|
+
|
|
53
|
+
if etype == NotificationTypes.EMAIL_NOTIFICATION.lower() and self._email is None:
|
|
54
|
+
try:
|
|
55
|
+
self._email = EmailReference.model_validate(payload)
|
|
56
|
+
self._notification_type = NotificationTypes.EMAIL_NOTIFICATION
|
|
57
|
+
except Exception:
|
|
58
|
+
self._email = None
|
|
59
|
+
|
|
60
|
+
if etype == NotificationTypes.WPX_COMMENT.lower() and self._wpx_comment is None:
|
|
61
|
+
try:
|
|
62
|
+
self._wpx_comment = WpxComment.model_validate(payload)
|
|
63
|
+
self._notification_type = NotificationTypes.WPX_COMMENT
|
|
64
|
+
except Exception:
|
|
65
|
+
self._wpx_comment = None
|
|
66
|
+
|
|
67
|
+
# Set notification type from activity name if not already set
|
|
68
|
+
if self._notification_type is None:
|
|
69
|
+
self._notification_type = (
|
|
70
|
+
NotificationTypes.AGENT_LIFECYCLE
|
|
71
|
+
if NotificationTypes(self.activity.name) is NotificationTypes.AGENT_LIFECYCLE
|
|
72
|
+
else None
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# ---- passthroughs ----
|
|
76
|
+
@property
|
|
77
|
+
def channel(self) -> Optional[str]:
|
|
78
|
+
"""The channel identifier from the activity's channel_id.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
The channel name (e.g., 'agents', 'msteams') or None if not available.
|
|
82
|
+
"""
|
|
83
|
+
ch = self.activity.channel_id
|
|
84
|
+
return ch.channel if ch else None
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def sub_channel(self) -> Optional[str]:
|
|
88
|
+
"""The subchannel identifier from the activity's channel_id.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
The subchannel name (e.g., 'email', 'word') or None if not available.
|
|
92
|
+
"""
|
|
93
|
+
ch = self.activity.channel_id
|
|
94
|
+
return ch.sub_channel if ch else None
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def value(self) -> Any:
|
|
98
|
+
"""The value payload from the activity.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
The activity's value, which may contain additional notification data.
|
|
102
|
+
"""
|
|
103
|
+
return self.activity.value
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def type(self) -> Optional[str]:
|
|
107
|
+
"""The activity type.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
The type of the activity (e.g., 'message', 'event') or None if not set.
|
|
111
|
+
"""
|
|
112
|
+
return self.activity.type
|
|
113
|
+
|
|
114
|
+
# --- typed entities available directly on the activity ---
|
|
115
|
+
@property
|
|
116
|
+
def email(self) -> Optional[EmailReference]:
|
|
117
|
+
"""The parsed email reference entity, if present.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
An EmailReference object if an email notification entity was found and
|
|
121
|
+
successfully parsed, otherwise None.
|
|
122
|
+
"""
|
|
123
|
+
return self._email
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def wpx_comment(self) -> Optional[WpxComment]:
|
|
127
|
+
"""The parsed Word/PowerPoint/Excel comment entity, if present.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
A WpxComment object if a comment entity was found and successfully parsed,
|
|
131
|
+
otherwise None.
|
|
132
|
+
"""
|
|
133
|
+
return self._wpx_comment
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def notification_type(self) -> Optional[NotificationTypes]:
|
|
137
|
+
"""The detected notification type.
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
The NotificationTypes enum value indicating the type of notification
|
|
141
|
+
(EMAIL_NOTIFICATION, WPX_COMMENT, or AGENT_LIFECYCLE), or None if the
|
|
142
|
+
notification type could not be determined.
|
|
143
|
+
"""
|
|
144
|
+
return self._notification_type
|
|
145
|
+
|
|
146
|
+
# Generic escape hatch
|
|
147
|
+
def as_model(self, model: Type[TModel]) -> Optional[TModel]:
|
|
148
|
+
"""Parse the activity value as a custom model type.
|
|
149
|
+
|
|
150
|
+
This method provides a generic way to validate and parse the activity's value
|
|
151
|
+
payload into any Pydantic model type. Useful for custom notification types not
|
|
152
|
+
directly supported by the typed properties.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
model: A Pydantic model class to validate and parse the activity value into.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
An instance of the specified model type if validation succeeds, otherwise None.
|
|
159
|
+
|
|
160
|
+
Example:
|
|
161
|
+
```python
|
|
162
|
+
from pydantic import BaseModel
|
|
163
|
+
|
|
164
|
+
class CustomNotification(BaseModel):
|
|
165
|
+
custom_field: str
|
|
166
|
+
|
|
167
|
+
notification = AgentNotificationActivity(activity)
|
|
168
|
+
custom = notification.as_model(CustomNotification)
|
|
169
|
+
if custom:
|
|
170
|
+
print(custom.custom_field)
|
|
171
|
+
```
|
|
172
|
+
"""
|
|
173
|
+
try:
|
|
174
|
+
return model.model_validate(self.value or {})
|
|
175
|
+
except Exception:
|
|
176
|
+
return None
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from enum import Enum
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AgentSubChannel(str, Enum):
|
|
8
|
+
"""Enumeration of agent subchannels within Microsoft 365 applications.
|
|
9
|
+
|
|
10
|
+
This enum defines the different subchannels through which agents can receive
|
|
11
|
+
notifications and messages from specific Microsoft 365 applications.
|
|
12
|
+
|
|
13
|
+
Attributes:
|
|
14
|
+
EMAIL: Email subchannel for Outlook-related notifications.
|
|
15
|
+
EXCEL: Excel subchannel for spreadsheet-related notifications.
|
|
16
|
+
WORD: Word subchannel for document-related notifications.
|
|
17
|
+
POWERPOINT: PowerPoint subchannel for presentation-related notifications.
|
|
18
|
+
FEDERATED_KNOWLEDGE_SERVICE: Federated Knowledge Service subchannel for
|
|
19
|
+
knowledge graph and search-related notifications.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
EMAIL = "email"
|
|
23
|
+
EXCEL = "excel"
|
|
24
|
+
WORD = "word"
|
|
25
|
+
POWERPOINT = "powerpoint"
|
|
26
|
+
FEDERATED_KNOWLEDGE_SERVICE = "federatedknowledgeservice"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from typing import Optional, Literal
|
|
5
|
+
from microsoft_agents.activity.entity import Entity
|
|
6
|
+
from .notification_types import NotificationTypes
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class EmailReference(Entity):
|
|
10
|
+
"""Entity representing an email notification reference.
|
|
11
|
+
|
|
12
|
+
This class encapsulates information about an email notification that an agent
|
|
13
|
+
receives from Outlook, including the email ID, conversation context, and content.
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
type: The notification type identifier, always set to "emailNotification".
|
|
17
|
+
id: The unique identifier of the email message.
|
|
18
|
+
conversation_id: The identifier of the conversation thread this email belongs to.
|
|
19
|
+
html_body: The HTML content of the email body.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
type: Literal["emailNotification"] = NotificationTypes.EMAIL_NOTIFICATION
|
|
23
|
+
id: Optional[str] = None
|
|
24
|
+
conversation_id: Optional[str] = None
|
|
25
|
+
html_body: Optional[str] = None
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
from microsoft_agents.activity.activity import Activity
|
|
7
|
+
from microsoft_agents.activity.entity import Entity
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class EmailResponse(Entity):
|
|
11
|
+
"""Entity representing an email response to be sent by an agent.
|
|
12
|
+
|
|
13
|
+
This class encapsulates the HTML content that will be sent as a response to an
|
|
14
|
+
email notification. It is used to construct reply messages in Outlook scenarios.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
type: The entity type identifier, always set to "emailResponse".
|
|
18
|
+
html_body: The HTML content of the email response body. Defaults to empty string.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
type: Literal["emailResponse"] = "emailResponse"
|
|
22
|
+
html_body: str = ""
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def create_email_response_activity(email_response_html_body: str) -> Activity:
|
|
26
|
+
"""Create a new Activity with an EmailResponse entity.
|
|
27
|
+
|
|
28
|
+
This factory method constructs a message activity containing an EmailResponse
|
|
29
|
+
entity, which can be sent back to respond to an email notification.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
email_response_html_body: The HTML content for the email response body.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
A new Activity instance with type set to 'message' and the EmailResponse
|
|
36
|
+
entity attached to its entities list.
|
|
37
|
+
|
|
38
|
+
Example:
|
|
39
|
+
```python
|
|
40
|
+
activity = EmailResponse.create_email_response_activity(
|
|
41
|
+
"<p>Thank you for your email. I'll get back to you soon.</p>"
|
|
42
|
+
)
|
|
43
|
+
await context.send_activity(activity)
|
|
44
|
+
```
|
|
45
|
+
"""
|
|
46
|
+
working_activity = Activity(type="message")
|
|
47
|
+
email_response = EmailResponse(html_body=email_response_html_body)
|
|
48
|
+
if working_activity.entities is None:
|
|
49
|
+
working_activity.entities = []
|
|
50
|
+
working_activity.entities.append(email_response)
|
|
51
|
+
return working_activity
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from enum import Enum
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class NotificationTypes(str, Enum):
|
|
8
|
+
"""Enumeration of notification types supported by Agent 365.
|
|
9
|
+
|
|
10
|
+
This enum defines the different types of notifications that agents can receive
|
|
11
|
+
from Microsoft 365 applications and services.
|
|
12
|
+
|
|
13
|
+
Attributes:
|
|
14
|
+
EMAIL_NOTIFICATION: Notification related to email events in Outlook.
|
|
15
|
+
WPX_COMMENT: Notification related to comments in Word, PowerPoint, or Excel.
|
|
16
|
+
AGENT_LIFECYCLE: Notification related to agent lifecycle events such as user
|
|
17
|
+
creation, deletion, or workload onboarding updates.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
EMAIL_NOTIFICATION = "emailNotification"
|
|
21
|
+
WPX_COMMENT = "wpxComment"
|
|
22
|
+
AGENT_LIFECYCLE = "agentLifecycle"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Copyright (c) Microsoft Corporation.
|
|
2
|
+
# Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
from typing import Optional, Literal
|
|
5
|
+
from microsoft_agents.activity.entity import Entity
|
|
6
|
+
from .notification_types import NotificationTypes
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class WpxComment(Entity):
|
|
10
|
+
"""Entity representing a comment notification from Word, PowerPoint, or Excel.
|
|
11
|
+
|
|
12
|
+
This class encapsulates information about a comment made in a Microsoft Office
|
|
13
|
+
document (Word, PowerPoint, or Excel), including the document context and
|
|
14
|
+
comment hierarchy.
|
|
15
|
+
|
|
16
|
+
Attributes:
|
|
17
|
+
type: The notification type identifier, always set to "wpxComment".
|
|
18
|
+
odata_id: The OData identifier for the comment resource.
|
|
19
|
+
document_id: The unique identifier of the document containing the comment.
|
|
20
|
+
parent_comment_id: The identifier of the parent comment, if this is a reply.
|
|
21
|
+
comment_id: The unique identifier of this comment.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
type: Literal["wpxComment"] = NotificationTypes.WPX_COMMENT
|
|
25
|
+
|
|
26
|
+
odata_id: Optional[str] = None
|
|
27
|
+
document_id: Optional[str] = None
|
|
28
|
+
parent_comment_id: Optional[str] = None
|
|
29
|
+
comment_id: Optional[str] = None
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
# Copyright (c) Microsoft Corporation.
|
|
2
|
-
# Licensed under the MIT License.
|
|
3
|
-
|
|
4
|
-
from __future__ import annotations
|
|
5
|
-
|
|
6
|
-
from collections.abc import Awaitable, Callable, Iterable
|
|
7
|
-
from typing import Any, TypeVar
|
|
8
|
-
|
|
9
|
-
from microsoft_agents.activity import ChannelId
|
|
10
|
-
from microsoft_agents.hosting.core import TurnContext
|
|
11
|
-
from microsoft_agents.hosting.core.app.state import TurnState
|
|
12
|
-
from .models.agent_notification_activity import AgentNotificationActivity, NotificationTypes
|
|
13
|
-
from .models.agent_subchannel import AgentSubChannel
|
|
14
|
-
from .models.agent_lifecycle_event import AgentLifecycleEvent
|
|
15
|
-
|
|
16
|
-
TContext = TypeVar("TContext", bound=TurnContext)
|
|
17
|
-
TState = TypeVar("TState", bound=TurnState)
|
|
18
|
-
|
|
19
|
-
AgentHandler = Callable[[TContext, TState, AgentNotificationActivity], Awaitable[None]]
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class AgentNotification:
|
|
23
|
-
def __init__(
|
|
24
|
-
self,
|
|
25
|
-
app: Any,
|
|
26
|
-
known_subchannels: Iterable[str | AgentSubChannel] | None = None,
|
|
27
|
-
known_lifecycle_events: Iterable[str | AgentLifecycleEvent] | None = None,
|
|
28
|
-
):
|
|
29
|
-
self._app = app
|
|
30
|
-
if known_subchannels is None:
|
|
31
|
-
source_subchannels: Iterable[str | AgentSubChannel] = AgentSubChannel
|
|
32
|
-
else:
|
|
33
|
-
source_subchannels = known_subchannels
|
|
34
|
-
|
|
35
|
-
self._known_subchannels = {
|
|
36
|
-
normalized
|
|
37
|
-
for normalized in (
|
|
38
|
-
self._normalize_subchannel(sub_channel) for sub_channel in source_subchannels
|
|
39
|
-
)
|
|
40
|
-
if normalized
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if known_lifecycle_events is None:
|
|
44
|
-
source_lifecycle_events: Iterable[str | AgentLifecycleEvent] = AgentLifecycleEvent
|
|
45
|
-
else:
|
|
46
|
-
source_lifecycle_events = known_lifecycle_events
|
|
47
|
-
|
|
48
|
-
self._known_lifecycle_events = {
|
|
49
|
-
normalized
|
|
50
|
-
for normalized in (
|
|
51
|
-
self._normalize_lifecycleevent(lifecycle_event)
|
|
52
|
-
for lifecycle_event in source_lifecycle_events
|
|
53
|
-
)
|
|
54
|
-
if normalized
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
def on_agent_notification(
|
|
58
|
-
self,
|
|
59
|
-
channel_id: ChannelId,
|
|
60
|
-
**kwargs: Any,
|
|
61
|
-
):
|
|
62
|
-
registered_channel = channel_id.channel.lower()
|
|
63
|
-
registered_subchannel = (channel_id.sub_channel or "*").lower()
|
|
64
|
-
|
|
65
|
-
def route_selector(context: TurnContext) -> bool:
|
|
66
|
-
ch = context.activity.channel_id
|
|
67
|
-
received_channel = (ch.channel if ch else "").lower()
|
|
68
|
-
received_subchannel = (ch.sub_channel if ch and ch.sub_channel else "").lower()
|
|
69
|
-
if received_channel != registered_channel:
|
|
70
|
-
return False
|
|
71
|
-
if registered_subchannel == "*":
|
|
72
|
-
return True
|
|
73
|
-
if registered_subchannel not in self._known_subchannels:
|
|
74
|
-
return False
|
|
75
|
-
return received_subchannel == registered_subchannel
|
|
76
|
-
|
|
77
|
-
def create_handler(handler: AgentHandler):
|
|
78
|
-
async def route_handler(context: TurnContext, state: TurnState):
|
|
79
|
-
ana = AgentNotificationActivity(context.activity)
|
|
80
|
-
await handler(context, state, ana)
|
|
81
|
-
|
|
82
|
-
return route_handler
|
|
83
|
-
|
|
84
|
-
def decorator(handler: AgentHandler):
|
|
85
|
-
route_handler = create_handler(handler)
|
|
86
|
-
self._app.add_route(route_selector, route_handler, **kwargs)
|
|
87
|
-
return route_handler
|
|
88
|
-
|
|
89
|
-
return decorator
|
|
90
|
-
|
|
91
|
-
def on_agent_lifecycle_notification(
|
|
92
|
-
self,
|
|
93
|
-
lifecycle_event: str,
|
|
94
|
-
**kwargs: Any,
|
|
95
|
-
):
|
|
96
|
-
def route_selector(context: TurnContext) -> bool:
|
|
97
|
-
ch = context.activity.channel_id
|
|
98
|
-
received_channel = ch.channel if ch else ""
|
|
99
|
-
received_channel = received_channel.lower()
|
|
100
|
-
if received_channel != "agents":
|
|
101
|
-
return False
|
|
102
|
-
if context.activity.name != NotificationTypes.AGENT_LIFECYCLE:
|
|
103
|
-
return False
|
|
104
|
-
if lifecycle_event == "*":
|
|
105
|
-
return True
|
|
106
|
-
if context.activity.value_type not in self._known_lifecycle_events:
|
|
107
|
-
return False
|
|
108
|
-
return True
|
|
109
|
-
|
|
110
|
-
def create_handler(handler: AgentHandler):
|
|
111
|
-
async def route_handler(context: TurnContext, state: TurnState):
|
|
112
|
-
ana = AgentNotificationActivity(context.activity)
|
|
113
|
-
await handler(context, state, ana)
|
|
114
|
-
|
|
115
|
-
return route_handler
|
|
116
|
-
|
|
117
|
-
def decorator(handler: AgentHandler):
|
|
118
|
-
route_handler = create_handler(handler)
|
|
119
|
-
self._app.add_route(route_selector, route_handler, **kwargs)
|
|
120
|
-
return route_handler
|
|
121
|
-
|
|
122
|
-
return decorator
|
|
123
|
-
|
|
124
|
-
def on_email(
|
|
125
|
-
self, **kwargs: Any
|
|
126
|
-
) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
|
|
127
|
-
return self.on_agent_notification(
|
|
128
|
-
ChannelId(channel="agents", sub_channel=AgentSubChannel.EMAIL), **kwargs
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
def on_word(
|
|
132
|
-
self, **kwargs: Any
|
|
133
|
-
) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
|
|
134
|
-
return self.on_agent_notification(
|
|
135
|
-
ChannelId(channel="agents", sub_channel=AgentSubChannel.WORD), **kwargs
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
def on_excel(
|
|
139
|
-
self, **kwargs: Any
|
|
140
|
-
) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
|
|
141
|
-
return self.on_agent_notification(
|
|
142
|
-
ChannelId(channel="agents", sub_channel=AgentSubChannel.EXCEL), **kwargs
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
def on_powerpoint(
|
|
146
|
-
self, **kwargs: Any
|
|
147
|
-
) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
|
|
148
|
-
return self.on_agent_notification(
|
|
149
|
-
ChannelId(channel="agents", sub_channel=AgentSubChannel.POWERPOINT), **kwargs
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
def on_lifecycle(
|
|
153
|
-
self, **kwargs: Any
|
|
154
|
-
) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
|
|
155
|
-
return self.on_lifecycle_notification("*", **kwargs)
|
|
156
|
-
|
|
157
|
-
def on_user_created(
|
|
158
|
-
self, **kwargs: Any
|
|
159
|
-
) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
|
|
160
|
-
return self.on_lifecycle_notification(AgentLifecycleEvent.USERCREATED, **kwargs)
|
|
161
|
-
|
|
162
|
-
def on_user_workload_onboarding(
|
|
163
|
-
self, **kwargs: Any
|
|
164
|
-
) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
|
|
165
|
-
return self.on_lifecycle_notification(
|
|
166
|
-
AgentLifecycleEvent.USERWORKLOADONBOARDINGUPDATED, **kwargs
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
def on_user_deleted(
|
|
170
|
-
self, **kwargs: Any
|
|
171
|
-
) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
|
|
172
|
-
return self.on_lifecycle_notification(AgentLifecycleEvent.USERDELETED, **kwargs)
|
|
173
|
-
|
|
174
|
-
@staticmethod
|
|
175
|
-
def _normalize_subchannel(value: str | AgentSubChannel | None) -> str:
|
|
176
|
-
if value is None:
|
|
177
|
-
return ""
|
|
178
|
-
resolved = value.value if isinstance(value, AgentSubChannel) else str(value)
|
|
179
|
-
return resolved.lower().strip()
|
|
180
|
-
|
|
181
|
-
@staticmethod
|
|
182
|
-
def _normalize_lifecycleevent(value: str | AgentLifecycleEvent | None) -> str:
|
|
183
|
-
if value is None:
|
|
184
|
-
return ""
|
|
185
|
-
resolved = value.value if isinstance(value, AgentLifecycleEvent) else str(value)
|
|
186
|
-
return resolved.lower().strip()
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
# Copyright (c) Microsoft Corporation.
|
|
2
|
-
# Licensed under the MIT License.
|
|
3
|
-
|
|
4
|
-
from enum import Enum
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class AgentLifecycleEvent(str, Enum):
|
|
8
|
-
USERCREATED = "agenticuseridentitycreated"
|
|
9
|
-
USERWORKLOADONBOARDINGUPDATED = "agenticuserworkloadonboardingupdated"
|
|
10
|
-
USERDELETED = "agenticuseridentitydeleted"
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
# Copyright (c) Microsoft Corporation.
|
|
2
|
-
# Licensed under the MIT License.
|
|
3
|
-
|
|
4
|
-
from typing import Any, Optional, Type, TypeVar
|
|
5
|
-
from microsoft_agents.activity import Activity
|
|
6
|
-
from .notification_types import NotificationTypes
|
|
7
|
-
from .email_reference import EmailReference
|
|
8
|
-
from .wpx_comment import WpxComment
|
|
9
|
-
|
|
10
|
-
TModel = TypeVar("TModel")
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class AgentNotificationActivity:
|
|
14
|
-
"""Light wrapper around an Activity object with typed entities parsed at create time."""
|
|
15
|
-
|
|
16
|
-
def __init__(self, activity: Activity):
|
|
17
|
-
if not activity:
|
|
18
|
-
raise ValueError("activity parameter is required and cannot be None")
|
|
19
|
-
self.activity = activity
|
|
20
|
-
self._email: Optional[EmailReference] = None
|
|
21
|
-
self._wpx_comment: Optional[WpxComment] = None
|
|
22
|
-
self._notification_type: Optional[NotificationTypes] = None
|
|
23
|
-
|
|
24
|
-
entities = self.activity.entities or []
|
|
25
|
-
for ent in entities:
|
|
26
|
-
etype = ent.type.lower()
|
|
27
|
-
payload = getattr(ent, "additional_properties", ent)
|
|
28
|
-
|
|
29
|
-
if etype == NotificationTypes.EMAIL_NOTIFICATION.lower() and self._email is None:
|
|
30
|
-
try:
|
|
31
|
-
self._email = EmailReference.model_validate(payload)
|
|
32
|
-
self._notification_type = NotificationTypes.EMAIL_NOTIFICATION
|
|
33
|
-
except Exception:
|
|
34
|
-
self._email = None
|
|
35
|
-
|
|
36
|
-
if etype == NotificationTypes.WPX_COMMENT.lower() and self._wpx_comment is None:
|
|
37
|
-
try:
|
|
38
|
-
self._wpx_comment = WpxComment.model_validate(payload)
|
|
39
|
-
self._notification_type = NotificationTypes.WPX_COMMENT
|
|
40
|
-
except Exception:
|
|
41
|
-
self._wpx_comment = None
|
|
42
|
-
|
|
43
|
-
# Set notification type from activity name if not already set
|
|
44
|
-
if self._notification_type is None:
|
|
45
|
-
self._notification_type = (
|
|
46
|
-
NotificationTypes.AGENT_LIFECYCLE
|
|
47
|
-
if NotificationTypes(self.activity.name) is NotificationTypes.AGENT_LIFECYCLE
|
|
48
|
-
else None
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
# ---- passthroughs ----
|
|
52
|
-
@property
|
|
53
|
-
def channel(self) -> Optional[str]:
|
|
54
|
-
ch = self.activity.channel_id
|
|
55
|
-
return ch.channel if ch else None
|
|
56
|
-
|
|
57
|
-
@property
|
|
58
|
-
def sub_channel(self) -> Optional[str]:
|
|
59
|
-
ch = self.activity.channel_id
|
|
60
|
-
return ch.sub_channel if ch else None
|
|
61
|
-
|
|
62
|
-
@property
|
|
63
|
-
def value(self) -> Any:
|
|
64
|
-
return self.activity.value
|
|
65
|
-
|
|
66
|
-
@property
|
|
67
|
-
def type(self) -> Optional[str]:
|
|
68
|
-
return self.activity.type
|
|
69
|
-
|
|
70
|
-
# --- typed entities available directly on the activity ---
|
|
71
|
-
@property
|
|
72
|
-
def email(self) -> Optional[EmailReference]:
|
|
73
|
-
return self._email
|
|
74
|
-
|
|
75
|
-
@property
|
|
76
|
-
def wpx_comment(self) -> Optional[WpxComment]:
|
|
77
|
-
return self._wpx_comment
|
|
78
|
-
|
|
79
|
-
@property
|
|
80
|
-
def notification_type(self) -> Optional[NotificationTypes]:
|
|
81
|
-
return self._notification_type
|
|
82
|
-
|
|
83
|
-
# Generic escape hatch
|
|
84
|
-
def as_model(self, model: Type[TModel]) -> Optional[TModel]:
|
|
85
|
-
try:
|
|
86
|
-
return model.model_validate(self.value or {})
|
|
87
|
-
except Exception:
|
|
88
|
-
return None
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
# Copyright (c) Microsoft Corporation.
|
|
2
|
-
# Licensed under the MIT License.
|
|
3
|
-
|
|
4
|
-
from enum import Enum
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class AgentSubChannel(str, Enum):
|
|
8
|
-
EMAIL = "email"
|
|
9
|
-
EXCEL = "excel"
|
|
10
|
-
WORD = "word"
|
|
11
|
-
POWERPOINT = "powerpoint"
|
|
12
|
-
FEDERATED_KNOWLEDGE_SERVICE = "federatedknowledgeservice"
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
# Copyright (c) Microsoft Corporation.
|
|
2
|
-
# Licensed under the MIT License.
|
|
3
|
-
|
|
4
|
-
from typing import Optional, Literal
|
|
5
|
-
from microsoft_agents.activity.entity import Entity
|
|
6
|
-
from .notification_types import NotificationTypes
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class EmailReference(Entity):
|
|
10
|
-
type: Literal["emailNotification"] = NotificationTypes.EMAIL_NOTIFICATION
|
|
11
|
-
id: Optional[str] = None
|
|
12
|
-
conversation_id: Optional[str] = None
|
|
13
|
-
html_body: Optional[str] = None
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# Copyright (c) Microsoft Corporation.
|
|
2
|
-
# Licensed under the MIT License.
|
|
3
|
-
|
|
4
|
-
from typing import Literal
|
|
5
|
-
from microsoft_agents.activity.activity import Activity
|
|
6
|
-
from microsoft_agents.activity.entity import Entity
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class EmailResponse(Entity):
|
|
10
|
-
type: Literal["emailResponse"] = "emailResponse"
|
|
11
|
-
html_body: str = ""
|
|
12
|
-
|
|
13
|
-
@staticmethod
|
|
14
|
-
def create_email_response_activity(email_response_html_body: str) -> Activity:
|
|
15
|
-
"""Create a new Activity with an EmailResponse entity.
|
|
16
|
-
|
|
17
|
-
Args:
|
|
18
|
-
email_response_html_body: The HTML content for the email response.
|
|
19
|
-
|
|
20
|
-
Returns:
|
|
21
|
-
A new Activity instance with type='message' and the EmailResponse entity attached.
|
|
22
|
-
"""
|
|
23
|
-
working_activity = Activity(type="message")
|
|
24
|
-
email_response = EmailResponse(html_body=email_response_html_body)
|
|
25
|
-
if working_activity.entities is None:
|
|
26
|
-
working_activity.entities = []
|
|
27
|
-
working_activity.entities.append(email_response)
|
|
28
|
-
return working_activity
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
# Copyright (c) Microsoft Corporation.
|
|
2
|
-
# Licensed under the MIT License.
|
|
3
|
-
|
|
4
|
-
from typing import Optional, Literal
|
|
5
|
-
from microsoft_agents.activity.entity import Entity
|
|
6
|
-
from .notification_types import NotificationTypes
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class WpxComment(Entity):
|
|
10
|
-
type: Literal["wpxComment"] = NotificationTypes.WPX_COMMENT
|
|
11
|
-
|
|
12
|
-
odata_id: Optional[str] = None
|
|
13
|
-
document_id: Optional[str] = None
|
|
14
|
-
parent_comment_id: Optional[str] = None
|
|
15
|
-
comment_id: Optional[str] = None
|
|
File without changes
|
|
@@ -10,19 +10,19 @@ This module provides utilities for handling agent notifications and routing.
|
|
|
10
10
|
|
|
11
11
|
# Main notification handler class
|
|
12
12
|
from .agent_notification import (
|
|
13
|
-
AgentNotification,
|
|
14
13
|
AgentHandler,
|
|
14
|
+
AgentNotification,
|
|
15
15
|
)
|
|
16
16
|
|
|
17
17
|
# Import all models from the models subpackage
|
|
18
18
|
from .models import (
|
|
19
|
+
AgentLifecycleEvent,
|
|
19
20
|
AgentNotificationActivity,
|
|
21
|
+
AgentSubChannel,
|
|
20
22
|
EmailReference,
|
|
21
|
-
WpxComment,
|
|
22
23
|
EmailResponse,
|
|
23
24
|
NotificationTypes,
|
|
24
|
-
|
|
25
|
-
AgentLifecycleEvent,
|
|
25
|
+
WpxComment,
|
|
26
26
|
)
|
|
27
27
|
|
|
28
28
|
__all__ = [
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|