Skip to content

Commit d42fbc9

Browse files
authored
Merge branch 'main' into dependabot/uv/uv-0.11.6
2 parents f533ad5 + 3b6a838 commit d42fbc9

4 files changed

Lines changed: 186 additions & 2 deletions

File tree

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

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,121 @@ async def handle_user_deleted(context, state, notification):
426426
"""
427427
return self.on_lifecycle_notification(AgentLifecycleEvent.USERDELETED, **kwargs)
428428

429+
def on_user_undeleted(
430+
self, **kwargs: Any
431+
) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
432+
"""Register a handler for user un-deletion lifecycle events.
433+
434+
This is a convenience decorator that registers a handler specifically for
435+
agentic user identity un-deletion events.
436+
437+
Args:
438+
**kwargs: Additional keyword arguments passed to the app's add_route method.
439+
440+
Returns:
441+
A decorator function that registers the handler with the application.
442+
443+
Example:
444+
```python
445+
@notifications.on_user_undeleted()
446+
async def handle_user_undeleted(context, state, notification):
447+
print("Agentic user identity un-deleted")
448+
```
449+
"""
450+
return self.on_lifecycle_notification(AgentLifecycleEvent.USERUNDELETED, **kwargs)
451+
452+
def on_user_updated(
453+
self, **kwargs: Any
454+
) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
455+
"""Register a handler for user updated lifecycle events.
456+
457+
This is a convenience decorator that registers a handler specifically for
458+
agentic user identity updated events.
459+
460+
Args:
461+
**kwargs: Additional keyword arguments passed to the app's add_route method.
462+
463+
Returns:
464+
A decorator function that registers the handler with the application.
465+
466+
Example:
467+
```python
468+
@notifications.on_user_updated()
469+
async def handle_user_updated(context, state, notification):
470+
print("Agentic user identity updated")
471+
```
472+
"""
473+
return self.on_lifecycle_notification(AgentLifecycleEvent.USERUPDATED, **kwargs)
474+
475+
def on_user_manager_updated(
476+
self, **kwargs: Any
477+
) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
478+
"""Register a handler for user manager updated lifecycle events.
479+
480+
This is a convenience decorator that registers a handler specifically for
481+
agentic user manager updated events.
482+
483+
Args:
484+
**kwargs: Additional keyword arguments passed to the app's add_route method.
485+
486+
Returns:
487+
A decorator function that registers the handler with the application.
488+
489+
Example:
490+
```python
491+
@notifications.on_user_manager_updated()
492+
async def handle_user_manager_updated(context, state, notification):
493+
print("Agentic user manager updated")
494+
```
495+
"""
496+
return self.on_lifecycle_notification(AgentLifecycleEvent.USERMANAGERUPDATED, **kwargs)
497+
498+
def on_user_enabled(
499+
self, **kwargs: Any
500+
) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
501+
"""Register a handler for user enabled lifecycle events.
502+
503+
This is a convenience decorator that registers a handler specifically for
504+
agentic user enabled events.
505+
506+
Args:
507+
**kwargs: Additional keyword arguments passed to the app's add_route method.
508+
509+
Returns:
510+
A decorator function that registers the handler with the application.
511+
512+
Example:
513+
```python
514+
@notifications.on_user_enabled()
515+
async def handle_user_enabled(context, state, notification):
516+
print("Agentic user enabled")
517+
```
518+
"""
519+
return self.on_lifecycle_notification(AgentLifecycleEvent.USERENABLED, **kwargs)
520+
521+
def on_user_disabled(
522+
self, **kwargs: Any
523+
) -> Callable[[AgentHandler], Callable[[TurnContext, TurnState], Awaitable[None]]]:
524+
"""Register a handler for user disabled lifecycle events.
525+
526+
This is a convenience decorator that registers a handler specifically for
527+
agentic user disabled events.
528+
529+
Args:
530+
**kwargs: Additional keyword arguments passed to the app's add_route method.
531+
532+
Returns:
533+
A decorator function that registers the handler with the application.
534+
535+
Example:
536+
```python
537+
@notifications.on_user_disabled()
538+
async def handle_user_disabled(context, state, notification):
539+
print("Agentic user disabled")
540+
```
541+
"""
542+
return self.on_lifecycle_notification(AgentLifecycleEvent.USERDISABLED, **kwargs)
543+
429544
@staticmethod
430545
def _normalize_subchannel(value: str | AgentSubChannel | None) -> str:
431546
"""Normalize a subchannel value to a lowercase string.

libraries/microsoft-agents-a365-notifications/microsoft_agents_a365/notifications/models/agent_lifecycle_event.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,18 @@ class AgentLifecycleEvent(str, Enum):
1515
USERWORKLOADONBOARDINGUPDATED: Event triggered when a user's workload
1616
onboarding status is updated.
1717
USERDELETED: Event triggered when an agentic user identity is deleted.
18+
USERUNDELETED: Event triggered when an agentic user identity is un-deleted.
19+
USERUPDATED: Event triggered when an agentic user identity is updated.
20+
USERMANAGERUPDATED: Event triggered when an agentic user's manager is updated.
21+
USERENABLED: Event triggered when an agentic user is enabled.
22+
USERDISABLED: Event triggered when an agentic user is disabled.
1823
"""
1924

2025
USERCREATED = "agenticuseridentitycreated"
2126
USERWORKLOADONBOARDINGUPDATED = "agenticuserworkloadonboardingupdated"
22-
USERDELETED = "agenticuseridentitydeleted"
27+
USERDELETED = "agenticuserdeleted"
28+
USERUNDELETED = "agenticuserundeleted"
29+
USERUPDATED = "agenticuseridentityupdated"
30+
USERMANAGERUPDATED = "agenticusermanagerupdated"
31+
USERENABLED = "agenticuserenabled"
32+
USERDISABLED = "agenticuserdisabled"

libraries/microsoft-agents-a365-observability-hosting/microsoft_agents_a365/observability/hosting/scope_helpers/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def get_caller_pairs(activity: Activity) -> Iterator[tuple[str, Any]]:
3636
frm = activity.from_property
3737
if not frm:
3838
return
39-
yield USER_ID_KEY, frm.aad_object_id
39+
yield USER_ID_KEY, frm.aad_object_id or frm.agentic_user_id or frm.id
4040
yield USER_NAME_KEY, frm.name
4141
yield USER_EMAIL_KEY, frm.agentic_user_id
4242

tests/observability/hosting/scope_helpers/test_scope_helper_utils.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,65 @@ def test_get_channel_pairs():
8383
assert (CHANNEL_LINK_KEY, None) in result
8484

8585

86+
def test_get_caller_pairs_non_teams_fallback_to_from_id():
87+
"""Test userId falls back to from.id when aad_object_id is None (non-Teams channel)."""
88+
from_account = ChannelAccount(
89+
id="from-id-123",
90+
name="Non-Teams User",
91+
)
92+
activity = Activity(type="message", from_property=from_account)
93+
94+
result = list(get_caller_pairs(activity))
95+
96+
assert (USER_ID_KEY, "from-id-123") in result
97+
assert (USER_NAME_KEY, "Non-Teams User") in result
98+
assert (USER_EMAIL_KEY, None) in result
99+
100+
101+
def test_get_caller_pairs_a2a_fallback_to_agentic_user_id():
102+
"""Test userId falls back to agentic_user_id for A2A calls (no aad_object_id)."""
103+
from_account = ChannelAccount(
104+
id="from-id-456",
105+
name="Agent Caller",
106+
agentic_user_id="a2a-agent-guid",
107+
)
108+
activity = Activity(type="message", from_property=from_account)
109+
110+
result = list(get_caller_pairs(activity))
111+
112+
assert (USER_ID_KEY, "a2a-agent-guid") in result
113+
assert (USER_EMAIL_KEY, "a2a-agent-guid") in result
114+
115+
116+
def test_get_caller_pairs_aad_object_id_wins_when_all_set():
117+
"""Test aad_object_id takes precedence when all identifiers are present."""
118+
from_account = ChannelAccount(
119+
id="from-id-789",
120+
aad_object_id="aad-wins",
121+
name="Full User",
122+
agentic_user_id="agent-upn",
123+
)
124+
activity = Activity(type="message", from_property=from_account)
125+
126+
result = list(get_caller_pairs(activity))
127+
128+
assert (USER_ID_KEY, "aad-wins") in result
129+
assert (USER_NAME_KEY, "Full User") in result
130+
assert (USER_EMAIL_KEY, "agent-upn") in result
131+
132+
133+
def test_get_caller_pairs_a2a_guid_agentic_user_id():
134+
"""Test userId resolves to GUID AgenticUserId in A2A scenario."""
135+
from_account = ChannelAccount(
136+
id="29:1sH5NArUwkWAX",
137+
name="Agent Caller",
138+
agentic_user_id="bef730f4-d6f5-4ffb-b759-26ffa449ed7e",
139+
)
140+
activity = Activity(type="message", from_property=from_account)
141+
result = list(get_caller_pairs(activity))
142+
assert (USER_ID_KEY, "bef730f4-d6f5-4ffb-b759-26ffa449ed7e") in result
143+
144+
86145
def test_get_conversation_pairs():
87146
"""Test get_conversation_pairs extracts conversation information."""
88147
conversation = ConversationAccount(id="conversation-123")

0 commit comments

Comments
 (0)