|
9 | 9 | from microsoft_agents.activity import ChannelId |
10 | 10 | from microsoft_agents.hosting.core import TurnContext |
11 | 11 | from microsoft_agents.hosting.core.app.state import TurnState |
| 12 | + |
| 13 | +from .models.agent_lifecycle_event import AgentLifecycleEvent |
12 | 14 | from .models.agent_notification_activity import AgentNotificationActivity, NotificationTypes |
13 | 15 | from .models.agent_subchannel import AgentSubChannel |
14 | | -from .models.agent_lifecycle_event import AgentLifecycleEvent |
15 | 16 |
|
16 | 17 | TContext = TypeVar("TContext", bound=TurnContext) |
17 | 18 | TState = TypeVar("TState", bound=TurnState) |
18 | 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 | +#: ``` |
19 | 42 | AgentHandler = Callable[[TContext, TState, AgentNotificationActivity], Awaitable[None]] |
20 | 43 |
|
21 | 44 |
|
22 | 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 | + |
23 | 75 | def __init__( |
24 | 76 | self, |
25 | 77 | app: Any, |
@@ -59,6 +111,31 @@ def on_agent_notification( |
59 | 111 | channel_id: ChannelId, |
60 | 112 | **kwargs: Any, |
61 | 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 | + """ |
62 | 139 | registered_channel = channel_id.channel.lower() |
63 | 140 | registered_subchannel = (channel_id.sub_channel or "*").lower() |
64 | 141 |
|
@@ -93,6 +170,27 @@ def on_agent_lifecycle_notification( |
93 | 170 | lifecycle_event: str, |
94 | 171 | **kwargs: Any, |
95 | 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 | + |
96 | 194 | def route_selector(context: TurnContext) -> bool: |
97 | 195 | ch = context.activity.channel_id |
98 | 196 | received_channel = ch.channel if ch else "" |
@@ -124,62 +222,235 @@ def decorator(handler: AgentHandler): |
124 | 222 | def on_email( |
125 | 223 | self, **kwargs: Any |
126 | 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 | + """ |
127 | 250 | return self.on_agent_notification( |
128 | 251 | ChannelId(channel="agents", sub_channel=AgentSubChannel.EMAIL), **kwargs |
129 | 252 | ) |
130 | 253 |
|
131 | 254 | def on_word( |
132 | 255 | self, **kwargs: Any |
133 | 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 | + """ |
134 | 277 | return self.on_agent_notification( |
135 | 278 | ChannelId(channel="agents", sub_channel=AgentSubChannel.WORD), **kwargs |
136 | 279 | ) |
137 | 280 |
|
138 | 281 | def on_excel( |
139 | 282 | self, **kwargs: Any |
140 | 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 | + """ |
141 | 304 | return self.on_agent_notification( |
142 | 305 | ChannelId(channel="agents", sub_channel=AgentSubChannel.EXCEL), **kwargs |
143 | 306 | ) |
144 | 307 |
|
145 | 308 | def on_powerpoint( |
146 | 309 | self, **kwargs: Any |
147 | 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 | + """ |
148 | 331 | return self.on_agent_notification( |
149 | 332 | ChannelId(channel="agents", sub_channel=AgentSubChannel.POWERPOINT), **kwargs |
150 | 333 | ) |
151 | 334 |
|
152 | 335 | def on_lifecycle( |
153 | 336 | self, **kwargs: Any |
154 | 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 | + """ |
155 | 356 | return self.on_lifecycle_notification("*", **kwargs) |
156 | 357 |
|
157 | 358 | def on_user_created( |
158 | 359 | self, **kwargs: Any |
159 | 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 | + """ |
160 | 379 | return self.on_lifecycle_notification(AgentLifecycleEvent.USERCREATED, **kwargs) |
161 | 380 |
|
162 | 381 | def on_user_workload_onboarding( |
163 | 382 | self, **kwargs: Any |
164 | 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 | + """ |
165 | 402 | return self.on_lifecycle_notification( |
166 | 403 | AgentLifecycleEvent.USERWORKLOADONBOARDINGUPDATED, **kwargs |
167 | 404 | ) |
168 | 405 |
|
169 | 406 | def on_user_deleted( |
170 | 407 | self, **kwargs: Any |
171 | 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 | + """ |
172 | 427 | return self.on_lifecycle_notification(AgentLifecycleEvent.USERDELETED, **kwargs) |
173 | 428 |
|
174 | 429 | @staticmethod |
175 | 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 | + """ |
176 | 439 | if value is None: |
177 | 440 | return "" |
178 | 441 | resolved = value.value if isinstance(value, AgentSubChannel) else str(value) |
179 | 442 | return resolved.lower().strip() |
180 | 443 |
|
181 | 444 | @staticmethod |
182 | 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 | + """ |
183 | 454 | if value is None: |
184 | 455 | return "" |
185 | 456 | resolved = value.value if isinstance(value, AgentLifecycleEvent) else str(value) |
|
0 commit comments