microsoft-agents-a365-notifications 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,39 @@
1
+ # Copyright (c) Microsoft. All rights reserved.
2
+
3
+ """
4
+ Microsoft Agent 365 Notifications
5
+
6
+ Notification and messaging extensions for AI agent applications.
7
+ This module provides utilities for handling agent notifications and routing.
8
+ """
9
+
10
+ # Main notification handler class
11
+ from .agent_notification import (
12
+ AgentNotification,
13
+ AgentHandler,
14
+ )
15
+
16
+ # Import all models from the models subpackage
17
+ from .models import (
18
+ AgentNotificationActivity,
19
+ EmailReference,
20
+ WpxComment,
21
+ EmailResponse,
22
+ NotificationTypes,
23
+ AgentSubChannel,
24
+ AgentLifecycleEvent,
25
+ )
26
+
27
+ __all__ = [
28
+ # Main notification handler
29
+ "AgentNotification",
30
+ "AgentHandler",
31
+ # Models and data classes
32
+ "AgentNotificationActivity",
33
+ "EmailReference",
34
+ "WpxComment",
35
+ "EmailResponse",
36
+ "NotificationTypes",
37
+ "AgentSubChannel",
38
+ "AgentLifecycleEvent",
39
+ ]
@@ -0,0 +1,183 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Awaitable, Callable, Iterable
4
+ from typing import Any, TypeVar
5
+
6
+ from microsoft_agents.activity import ChannelId
7
+ from microsoft_agents.hosting.core import TurnContext
8
+ from microsoft_agents.hosting.core.app.state import TurnState
9
+ from .models.agent_notification_activity import AgentNotificationActivity, NotificationTypes
10
+ from .models.agent_subchannel import AgentSubChannel
11
+ from .models.agent_lifecycle_event import AgentLifecycleEvent
12
+
13
+ TContext = TypeVar("TContext", bound=TurnContext)
14
+ TState = TypeVar("TState", bound=TurnState)
15
+
16
+ AgentHandler = Callable[[TContext, TState, AgentNotificationActivity], Awaitable[None]]
17
+
18
+
19
+ class AgentNotification:
20
+ def __init__(
21
+ self,
22
+ app: Any,
23
+ known_subchannels: Iterable[str | AgentSubChannel] | None = None,
24
+ known_lifecycle_events: Iterable[str | AgentLifecycleEvent] | None = None,
25
+ ):
26
+ self._app = app
27
+ if known_subchannels is None:
28
+ source_subchannels: Iterable[str | AgentSubChannel] = AgentSubChannel
29
+ else:
30
+ source_subchannels = known_subchannels
31
+
32
+ self._known_subchannels = {
33
+ normalized
34
+ for normalized in (
35
+ self._normalize_subchannel(sub_channel) for sub_channel in source_subchannels
36
+ )
37
+ if normalized
38
+ }
39
+
40
+ if known_lifecycle_events is None:
41
+ source_lifecycle_events: Iterable[str | AgentLifecycleEvent] = AgentLifecycleEvent
42
+ else:
43
+ source_lifecycle_events = known_lifecycle_events
44
+
45
+ self._known_lifecycle_events = {
46
+ normalized
47
+ for normalized in (
48
+ self._normalize_lifecycleevent(lifecycle_event)
49
+ for lifecycle_event in source_lifecycle_events
50
+ )
51
+ if normalized
52
+ }
53
+
54
+ def on_agent_notification(
55
+ self,
56
+ channel_id: ChannelId,
57
+ **kwargs: Any,
58
+ ):
59
+ registered_channel = channel_id.channel.lower()
60
+ registered_subchannel = (channel_id.sub_channel or "*").lower()
61
+
62
+ def route_selector(context: TurnContext) -> bool:
63
+ ch = context.activity.channel_id
64
+ received_channel = (ch.channel if ch else "").lower()
65
+ received_subchannel = (ch.sub_channel if ch and ch.sub_channel else "").lower()
66
+ if received_channel != registered_channel:
67
+ return False
68
+ if registered_subchannel == "*":
69
+ return True
70
+ if registered_subchannel not in self._known_subchannels:
71
+ return False
72
+ return received_subchannel == registered_subchannel
73
+
74
+ def create_handler(handler: AgentHandler):
75
+ async def route_handler(context: TurnContext, state: TurnState):
76
+ ana = AgentNotificationActivity(context.activity)
77
+ await handler(context, state, ana)
78
+
79
+ return route_handler
80
+
81
+ def decorator(handler: AgentHandler):
82
+ route_handler = create_handler(handler)
83
+ self._app.add_route(route_selector, route_handler, **kwargs)
84
+ return route_handler
85
+
86
+ return decorator
87
+
88
+ def on_agent_lifecycle_notification(
89
+ self,
90
+ lifecycle_event: str,
91
+ **kwargs: Any,
92
+ ):
93
+ def route_selector(context: TurnContext) -> bool:
94
+ ch = context.activity.channel_id
95
+ received_channel = ch.channel if ch else ""
96
+ received_channel = received_channel.lower()
97
+ if received_channel != "agents":
98
+ return False
99
+ if context.activity.name != NotificationTypes.AGENT_LIFECYCLE:
100
+ return False
101
+ if lifecycle_event == "*":
102
+ return True
103
+ if context.activity.value_type not in self._known_lifecycle_events:
104
+ return False
105
+ return True
106
+
107
+ def create_handler(handler: AgentHandler):
108
+ async def route_handler(context: TurnContext, state: TurnState):
109
+ ana = AgentNotificationActivity(context.activity)
110
+ await handler(context, state, ana)
111
+
112
+ return route_handler
113
+
114
+ def decorator(handler: AgentHandler):
115
+ route_handler = create_handler(handler)
116
+ self._app.add_route(route_selector, route_handler, **kwargs)
117
+ return route_handler
118
+
119
+ return decorator
120
+
121
+ def on_email(
122
+ self, **kwargs: Any
123
+ ) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
124
+ return self.on_agent_notification(
125
+ ChannelId(channel="agents", sub_channel=AgentSubChannel.EMAIL), **kwargs
126
+ )
127
+
128
+ def on_word(
129
+ self, **kwargs: Any
130
+ ) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
131
+ return self.on_agent_notification(
132
+ ChannelId(channel="agents", sub_channel=AgentSubChannel.WORD), **kwargs
133
+ )
134
+
135
+ def on_excel(
136
+ self, **kwargs: Any
137
+ ) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
138
+ return self.on_agent_notification(
139
+ ChannelId(channel="agents", sub_channel=AgentSubChannel.EXCEL), **kwargs
140
+ )
141
+
142
+ def on_powerpoint(
143
+ self, **kwargs: Any
144
+ ) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
145
+ return self.on_agent_notification(
146
+ ChannelId(channel="agents", sub_channel=AgentSubChannel.POWERPOINT), **kwargs
147
+ )
148
+
149
+ def on_lifecycle(
150
+ self, **kwargs: Any
151
+ ) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
152
+ return self.on_lifecycle_notification("*", **kwargs)
153
+
154
+ def on_user_created(
155
+ self, **kwargs: Any
156
+ ) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
157
+ return self.on_lifecycle_notification(AgentLifecycleEvent.USERCREATED, **kwargs)
158
+
159
+ def on_user_workload_onboarding(
160
+ self, **kwargs: Any
161
+ ) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
162
+ return self.on_lifecycle_notification(
163
+ AgentLifecycleEvent.USERWORKLOADONBOARDINGUPDATED, **kwargs
164
+ )
165
+
166
+ def on_user_deleted(
167
+ self, **kwargs: Any
168
+ ) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
169
+ return self.on_lifecycle_notification(AgentLifecycleEvent.USERDELETED, **kwargs)
170
+
171
+ @staticmethod
172
+ def _normalize_subchannel(value: str | AgentSubChannel | None) -> str:
173
+ if value is None:
174
+ return ""
175
+ resolved = value.value if isinstance(value, AgentSubChannel) else str(value)
176
+ return resolved.lower().strip()
177
+
178
+ @staticmethod
179
+ def _normalize_lifecycleevent(value: str | AgentLifecycleEvent | None) -> str:
180
+ if value is None:
181
+ return ""
182
+ resolved = value.value if isinstance(value, AgentLifecycleEvent) else str(value)
183
+ return resolved.lower().strip()
@@ -0,0 +1,17 @@
1
+ from .agent_notification_activity import AgentNotificationActivity
2
+ from .email_reference import EmailReference
3
+ from .wpx_comment import WpxComment
4
+ from .email_response import EmailResponse
5
+ from .notification_types import NotificationTypes
6
+ from .agent_subchannel import AgentSubChannel
7
+ from .agent_lifecycle_event import AgentLifecycleEvent
8
+
9
+ __all__ = [
10
+ "AgentNotificationActivity",
11
+ "EmailReference",
12
+ "WpxComment",
13
+ "EmailResponse",
14
+ "NotificationTypes",
15
+ "AgentSubChannel",
16
+ "AgentLifecycleEvent",
17
+ ]
@@ -0,0 +1,7 @@
1
+ from enum import Enum
2
+
3
+
4
+ class AgentLifecycleEvent(str, Enum):
5
+ USERCREATED = "agenticuseridentitycreated"
6
+ USERWORKLOADONBOARDINGUPDATED = "agenticuserworkloadonboardingupdated"
7
+ USERDELETED = "agenticuseridentitydeleted"
@@ -0,0 +1,85 @@
1
+ from typing import Any, Optional, Type, TypeVar
2
+ from microsoft_agents.activity import Activity
3
+ from .notification_types import NotificationTypes
4
+ from .email_reference import EmailReference
5
+ from .wpx_comment import WpxComment
6
+
7
+ TModel = TypeVar("TModel")
8
+
9
+
10
+ class AgentNotificationActivity:
11
+ """Light wrapper around an Activity object with typed entities parsed at create time."""
12
+
13
+ def __init__(self, activity: Activity):
14
+ if not activity:
15
+ raise ValueError("activity parameter is required and cannot be None")
16
+ self.activity = activity
17
+ self._email: Optional[EmailReference] = None
18
+ self._wpx_comment: Optional[WpxComment] = None
19
+ self._notification_type: Optional[NotificationTypes] = None
20
+
21
+ entities = self.activity.entities or []
22
+ for ent in entities:
23
+ etype = ent.type.lower()
24
+ payload = getattr(ent, "additional_properties", ent)
25
+
26
+ if etype == NotificationTypes.EMAIL_NOTIFICATION.lower() and self._email is None:
27
+ try:
28
+ self._email = EmailReference.model_validate(payload)
29
+ self._notification_type = NotificationTypes.EMAIL_NOTIFICATION
30
+ except Exception:
31
+ self._email = None
32
+
33
+ if etype == NotificationTypes.WPX_COMMENT.lower() and self._wpx_comment is None:
34
+ try:
35
+ self._wpx_comment = WpxComment.model_validate(payload)
36
+ self._notification_type = NotificationTypes.WPX_COMMENT
37
+ except Exception:
38
+ self._wpx_comment = None
39
+
40
+ # Set notification type from activity name if not already set
41
+ if self._notification_type is None:
42
+ self._notification_type = (
43
+ NotificationTypes.AGENT_LIFECYCLE
44
+ if NotificationTypes(self.activity.name) is NotificationTypes.AGENT_LIFECYCLE
45
+ else None
46
+ )
47
+
48
+ # ---- passthroughs ----
49
+ @property
50
+ def channel(self) -> Optional[str]:
51
+ ch = self.activity.channel_id
52
+ return ch.channel if ch else None
53
+
54
+ @property
55
+ def sub_channel(self) -> Optional[str]:
56
+ ch = self.activity.channel_id
57
+ return ch.sub_channel if ch else None
58
+
59
+ @property
60
+ def value(self) -> Any:
61
+ return self.activity.value
62
+
63
+ @property
64
+ def type(self) -> Optional[str]:
65
+ return self.activity.type
66
+
67
+ # --- typed entities available directly on the activity ---
68
+ @property
69
+ def email(self) -> Optional[EmailReference]:
70
+ return self._email
71
+
72
+ @property
73
+ def wpx_comment(self) -> Optional[WpxComment]:
74
+ return self._wpx_comment
75
+
76
+ @property
77
+ def notification_type(self) -> Optional[NotificationTypes]:
78
+ return self._notification_type
79
+
80
+ # Generic escape hatch
81
+ def as_model(self, model: Type[TModel]) -> Optional[TModel]:
82
+ try:
83
+ return model.model_validate(self.value or {})
84
+ except Exception:
85
+ return None
@@ -0,0 +1,9 @@
1
+ from enum import Enum
2
+
3
+
4
+ class AgentSubChannel(str, Enum):
5
+ EMAIL = "email"
6
+ EXCEL = "excel"
7
+ WORD = "word"
8
+ POWERPOINT = "powerpoint"
9
+ FEDERATED_KNOWLEDGE_SERVICE = "federatedknowledgeservice"
@@ -0,0 +1,10 @@
1
+ from typing import Optional, Literal
2
+ from microsoft_agents.activity.entity import Entity
3
+ from .notification_types import NotificationTypes
4
+
5
+
6
+ class EmailReference(Entity):
7
+ type: Literal["emailNotification"] = NotificationTypes.EMAIL_NOTIFICATION
8
+ id: Optional[str] = None
9
+ conversation_id: Optional[str] = None
10
+ html_body: Optional[str] = None
@@ -0,0 +1,25 @@
1
+ from typing import Literal
2
+ from microsoft_agents.activity.activity import Activity
3
+ from microsoft_agents.activity.entity import Entity
4
+
5
+
6
+ class EmailResponse(Entity):
7
+ type: Literal["emailResponse"] = "emailResponse"
8
+ html_body: str = ""
9
+
10
+ @staticmethod
11
+ def create_email_response_activity(email_response_html_body: str) -> Activity:
12
+ """Create a new Activity with an EmailResponse entity.
13
+
14
+ Args:
15
+ email_response_html_body: The HTML content for the email response.
16
+
17
+ Returns:
18
+ A new Activity instance with type='message' and the EmailResponse entity attached.
19
+ """
20
+ working_activity = Activity(type="message")
21
+ email_response = EmailResponse(html_body=email_response_html_body)
22
+ if working_activity.entities is None:
23
+ working_activity.entities = []
24
+ working_activity.entities.append(email_response)
25
+ return working_activity
@@ -0,0 +1,7 @@
1
+ from enum import Enum
2
+
3
+
4
+ class NotificationTypes(str, Enum):
5
+ EMAIL_NOTIFICATION = "emailNotification"
6
+ WPX_COMMENT = "wpxComment"
7
+ AGENT_LIFECYCLE = "agentLifecycle"
@@ -0,0 +1,12 @@
1
+ from typing import Optional, Literal
2
+ from microsoft_agents.activity.entity import Entity
3
+ from .notification_types import NotificationTypes
4
+
5
+
6
+ class WpxComment(Entity):
7
+ type: Literal["wpxComment"] = NotificationTypes.WPX_COMMENT
8
+
9
+ odata_id: Optional[str] = None
10
+ document_id: Optional[str] = None
11
+ parent_comment_id: Optional[str] = None
12
+ comment_id: Optional[str] = None
@@ -0,0 +1,70 @@
1
+ Metadata-Version: 2.4
2
+ Name: microsoft-agents-a365-notifications
3
+ Version: 0.1.0
4
+ Summary: Notification and messaging extensions for AI agent applications
5
+ Author-email: Microsoft <support@microsoft.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/microsoft/Agent365-python
8
+ Project-URL: Repository, https://github.com/microsoft/Agent365-python
9
+ Project-URL: Issues, https://github.com/microsoft/Agent365-python/issues
10
+ Project-URL: Documentation, https://github.com/microsoft/Agent365-python/tree/main/libraries/microsoft-agents-a365-notifications
11
+ Keywords: notifications,messaging,agents,ai,routing,sdk
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Classifier: Topic :: Communications
21
+ Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
22
+ Requires-Python: >=3.11
23
+ Description-Content-Type: text/markdown
24
+ Requires-Dist: typing-extensions>=4.0.0
25
+ Requires-Dist: microsoft-agents-activity>=0.4.0
26
+ Requires-Dist: microsoft-agents-hosting-core>=0.4.0
27
+ Requires-Dist: pydantic>=2.0.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
30
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
31
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
32
+ Requires-Dist: black>=23.0.0; extra == "dev"
33
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
34
+ Provides-Extra: test
35
+ Requires-Dist: pytest>=7.0.0; extra == "test"
36
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "test"
37
+
38
+ # microsoft-agents-a365-notifications
39
+
40
+ [![PyPI](https://img.shields.io/pypi/v/microsoft-agents-a365-notifications?label=PyPI&logo=pypi)](https://pypi.org/project/microsoft-agents-a365-notifications)
41
+ [![PyPI Downloads](https://img.shields.io/pypi/dm/microsoft-agents-a365-notifications?label=Downloads&logo=pypi)](https://pypi.org/project/microsoft-agents-a365-notifications)
42
+
43
+ Notification and messaging extensions for AI agent applications. This package provides utilities for handling agent notifications, lifecycle events, and routing across different channels and subchannels in Microsoft 365 applications.
44
+
45
+ ## Installation
46
+
47
+ ```bash
48
+ pip install microsoft-agents-a365-notifications
49
+ ```
50
+
51
+ ## Usage
52
+
53
+ For usage examples and detailed documentation, see the [Notification documentation](https://learn.microsoft.com/microsoft-agent-365/developer/notification?tabs=python) on Microsoft Learn.
54
+
55
+ ## Support
56
+
57
+ For issues, questions, or feedback:
58
+
59
+ - File issues in the [GitHub Issues](https://github.com/microsoft/Agent365-python/issues) section
60
+ - See the [main documentation](../../../README.md) for more information
61
+
62
+ ## Trademarks
63
+
64
+ *Microsoft, Windows, Microsoft Azure and/or other Microsoft products and services referenced in the documentation may be either trademarks or registered trademarks of Microsoft in the United States and/or other countries. The licenses for this project do not grant you rights to use any Microsoft names, logos, or trademarks. Microsoft's general trademark guidelines can be found at http://go.microsoft.com/fwlink/?LinkID=254653.*
65
+
66
+ ## License
67
+
68
+ Copyright (c) Microsoft Corporation. All rights reserved.
69
+
70
+ Licensed under the MIT License - see the [LICENSE](../../../LICENSE.md) file for details.
@@ -0,0 +1,14 @@
1
+ microsoft_agents_a365/notifications/__init__.py,sha256=ppyGNL9uLOzwaMy9v83UDYT-f2HNrA9H_yxf8rEwfQU,864
2
+ microsoft_agents_a365/notifications/agent_notification.py,sha256=_3Ice9EjzgHWD-_4XlIfP9PiH28eZuZe4YZV90N-obw,6995
3
+ microsoft_agents_a365/notifications/models/__init__.py,sha256=tVhQU7QuOtLk_Ihz5UaHhcHfDXAjR81iRV1v4aMI_fw,524
4
+ microsoft_agents_a365/notifications/models/agent_lifecycle_event.py,sha256=0UZ39c_HnwJgDZmNaLRGkjabCC1kNiiwC_eT8OnOazU,231
5
+ microsoft_agents_a365/notifications/models/agent_notification_activity.py,sha256=oRo-M_NYk7mf5039oW2JtV_pnYdDfUGc612jMV9YLDE,3008
6
+ microsoft_agents_a365/notifications/models/agent_subchannel.py,sha256=Ra-nZdJgFExb6_ArVXJ-4lbW_XjuzRaLdleHifyMw6w,208
7
+ microsoft_agents_a365/notifications/models/email_reference.py,sha256=DWuzqT-DDns3YsE20JgRFb7xS8mvUgJZ0FAfqpNMVNA,356
8
+ microsoft_agents_a365/notifications/models/email_response.py,sha256=470zqPN6ElhZJf-mGWfa9z6BWbSEvWxxNYJRI_oOsAY,932
9
+ microsoft_agents_a365/notifications/models/notification_types.py,sha256=-xgwGVzuqnzLdxnM25x6WDJeUKwIXwmzcrzs8nRwdSA,175
10
+ microsoft_agents_a365/notifications/models/wpx_comment.py,sha256=KKg9o4Y7di7D1cGojDlV7k_FRntN8W-BGkJVUhpRm6w,386
11
+ microsoft_agents_a365_notifications-0.1.0.dist-info/METADATA,sha256=chcCj_8asPZEzKuYjHEbULZiceq60KqbL1TQ3G2Vq1o,3459
12
+ microsoft_agents_a365_notifications-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
13
+ microsoft_agents_a365_notifications-0.1.0.dist-info/top_level.txt,sha256=G3c2_4sy5_EM_BWO67SbK2tKj4G8XFn-QXRbh8g9Lgk,22
14
+ microsoft_agents_a365_notifications-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ microsoft_agents_a365