Skip to content

Commit a3cbd62

Browse files
authored
Merge branch 'main' into users/jterrazas/google-adk-support
2 parents e6ca941 + 66e7be6 commit a3cbd62

File tree

11 files changed

+466
-12
lines changed

11 files changed

+466
-12
lines changed

libraries/microsoft-agents-a365-notifications/microsoft_agents_a365/notifications/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,19 @@
1010

1111
# Main notification handler class
1212
from .agent_notification import (
13-
AgentNotification,
1413
AgentHandler,
14+
AgentNotification,
1515
)
1616

1717
# Import all models from the models subpackage
1818
from .models import (
19+
AgentLifecycleEvent,
1920
AgentNotificationActivity,
21+
AgentSubChannel,
2022
EmailReference,
21-
WpxComment,
2223
EmailResponse,
2324
NotificationTypes,
24-
AgentSubChannel,
25-
AgentLifecycleEvent,
25+
WpxComment,
2626
)
2727

2828
__all__ = [

libraries/microsoft-agents-a365-notifications/microsoft_agents_a365/notifications/agent_notification.py

Lines changed: 272 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,69 @@
99
from microsoft_agents.activity import ChannelId
1010
from microsoft_agents.hosting.core import TurnContext
1111
from microsoft_agents.hosting.core.app.state import TurnState
12+
13+
from .models.agent_lifecycle_event import AgentLifecycleEvent
1214
from .models.agent_notification_activity import AgentNotificationActivity, NotificationTypes
1315
from .models.agent_subchannel import AgentSubChannel
14-
from .models.agent_lifecycle_event import AgentLifecycleEvent
1516

1617
TContext = TypeVar("TContext", bound=TurnContext)
1718
TState = TypeVar("TState", bound=TurnState)
1819

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+
#: ```
1942
AgentHandler = Callable[[TContext, TState, AgentNotificationActivity], Awaitable[None]]
2043

2144

2245
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+
2375
def __init__(
2476
self,
2577
app: Any,
@@ -59,6 +111,31 @@ def on_agent_notification(
59111
channel_id: ChannelId,
60112
**kwargs: Any,
61113
):
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+
"""
62139
registered_channel = channel_id.channel.lower()
63140
registered_subchannel = (channel_id.sub_channel or "*").lower()
64141

@@ -93,6 +170,27 @@ def on_agent_lifecycle_notification(
93170
lifecycle_event: str,
94171
**kwargs: Any,
95172
):
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+
96194
def route_selector(context: TurnContext) -> bool:
97195
ch = context.activity.channel_id
98196
received_channel = ch.channel if ch else ""
@@ -124,62 +222,235 @@ def decorator(handler: AgentHandler):
124222
def on_email(
125223
self, **kwargs: Any
126224
) -> 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+
"""
127250
return self.on_agent_notification(
128251
ChannelId(channel="agents", sub_channel=AgentSubChannel.EMAIL), **kwargs
129252
)
130253

131254
def on_word(
132255
self, **kwargs: Any
133256
) -> 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+
"""
134277
return self.on_agent_notification(
135278
ChannelId(channel="agents", sub_channel=AgentSubChannel.WORD), **kwargs
136279
)
137280

138281
def on_excel(
139282
self, **kwargs: Any
140283
) -> 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+
"""
141304
return self.on_agent_notification(
142305
ChannelId(channel="agents", sub_channel=AgentSubChannel.EXCEL), **kwargs
143306
)
144307

145308
def on_powerpoint(
146309
self, **kwargs: Any
147310
) -> 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+
"""
148331
return self.on_agent_notification(
149332
ChannelId(channel="agents", sub_channel=AgentSubChannel.POWERPOINT), **kwargs
150333
)
151334

152335
def on_lifecycle(
153336
self, **kwargs: Any
154337
) -> 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+
"""
155356
return self.on_lifecycle_notification("*", **kwargs)
156357

157358
def on_user_created(
158359
self, **kwargs: Any
159360
) -> 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+
"""
160379
return self.on_lifecycle_notification(AgentLifecycleEvent.USERCREATED, **kwargs)
161380

162381
def on_user_workload_onboarding(
163382
self, **kwargs: Any
164383
) -> 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+
"""
165402
return self.on_lifecycle_notification(
166403
AgentLifecycleEvent.USERWORKLOADONBOARDINGUPDATED, **kwargs
167404
)
168405

169406
def on_user_deleted(
170407
self, **kwargs: Any
171408
) -> 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+
"""
172427
return self.on_lifecycle_notification(AgentLifecycleEvent.USERDELETED, **kwargs)
173428

174429
@staticmethod
175430
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+
"""
176439
if value is None:
177440
return ""
178441
resolved = value.value if isinstance(value, AgentSubChannel) else str(value)
179442
return resolved.lower().strip()
180443

181444
@staticmethod
182445
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+
"""
183454
if value is None:
184455
return ""
185456
resolved = value.value if isinstance(value, AgentLifecycleEvent) else str(value)

0 commit comments

Comments
 (0)