microsoft-agents-activity 1.2.0.dev4__py3-none-any.whl → 1.2.0.dev7__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.
@@ -6,7 +6,8 @@ from __future__ import annotations
6
6
  import logging
7
7
  from copy import copy
8
8
  from datetime import datetime, timezone
9
- from typing import Optional, Any
9
+ from typing import Optional, Any, cast, Annotated, TypeVar
10
+ from typing_extensions import Self
10
11
 
11
12
  from pydantic import (
12
13
  Field,
@@ -48,6 +49,8 @@ from microsoft_agents.activity.errors import activity_errors
48
49
 
49
50
  logger = logging.getLogger(__name__)
50
51
 
52
+ _EntityT = TypeVar("_EntityT", bound=Entity)
53
+
51
54
 
52
55
  # TODO: A2A Agent 2 is responding with None as id, had to mark it as optional (investigate)
53
56
  class Activity(AgentsModel, _ChannelIdFieldMixin):
@@ -157,7 +160,7 @@ class Activity(AgentsModel, _ChannelIdFieldMixin):
157
160
  local_timestamp: datetime = None
158
161
  local_timezone: NonEmptyString = None
159
162
  service_url: NonEmptyString = None
160
- from_property: ChannelAccount = Field(None, alias="from")
163
+ from_property: Annotated[ChannelAccount, Field(alias="from")] = None
161
164
  conversation: ConversationAccount = None
162
165
  recipient: ChannelAccount = None
163
166
  text_format: NonEmptyString = None
@@ -509,33 +512,36 @@ class Activity(AgentsModel, _ChannelIdFieldMixin):
509
512
  .. remarks::
510
513
  The new activity sets up routing information based on this activity.
511
514
  """
512
- return pick_model(
513
- Activity,
514
- type=ActivityTypes.message,
515
- timestamp=datetime.now(timezone.utc),
516
- from_property=SkipNone(
517
- ChannelAccount.pick_properties(self.recipient, ["id", "name"])
518
- ),
519
- recipient=SkipNone(
520
- ChannelAccount.pick_properties(self.from_property, ["id", "name"])
521
- ),
522
- reply_to_id=(
523
- SkipNone(self.id)
524
- if type != ActivityTypes.conversation_update
525
- or self.channel_id not in ["directline", "webchat"]
526
- else None
515
+ return cast(
516
+ Self,
517
+ pick_model(
518
+ self.__class__,
519
+ type=ActivityTypes.message,
520
+ timestamp=datetime.now(timezone.utc),
521
+ from_property=SkipNone(
522
+ ChannelAccount.pick_properties(self.recipient, ["id", "name"])
523
+ ),
524
+ recipient=SkipNone(
525
+ ChannelAccount.pick_properties(self.from_property, ["id", "name"])
526
+ ),
527
+ reply_to_id=(
528
+ SkipNone(self.id)
529
+ if self.type != ActivityTypes.conversation_update
530
+ or self.channel_id not in ["directline", "webchat"]
531
+ else None
532
+ ),
533
+ service_url=self.service_url,
534
+ channel_id=self.channel_id,
535
+ conversation=SkipNone(
536
+ ConversationAccount.pick_properties(
537
+ self.conversation, ["is_group", "id", "name"]
538
+ )
539
+ ),
540
+ text=text if text else "",
541
+ locale=locale if locale else SkipNone(self.locale),
542
+ attachments=[],
543
+ entities=[],
527
544
  ),
528
- service_url=self.service_url,
529
- channel_id=self.channel_id,
530
- conversation=SkipNone(
531
- ConversationAccount.pick_properties(
532
- self.conversation, ["is_group", "id", "name"]
533
- )
534
- ),
535
- text=text if text else "",
536
- locale=locale if locale else SkipNone(self.locale),
537
- attachments=[],
538
- entities=[],
539
545
  )
540
546
 
541
547
  def create_trace(
@@ -558,33 +564,36 @@ class Activity(AgentsModel, _ChannelIdFieldMixin):
558
564
  if not value_type and value:
559
565
  value_type = type(value).__name__
560
566
 
561
- return pick_model(
562
- Activity,
563
- type=ActivityTypes.trace,
564
- timestamp=datetime.now(timezone.utc),
565
- from_property=SkipNone(
566
- ChannelAccount.pick_properties(self.recipient, ["id", "name"])
567
- ),
568
- recipient=SkipNone(
569
- ChannelAccount.pick_properties(self.from_property, ["id", "name"])
567
+ return cast(
568
+ Self,
569
+ pick_model(
570
+ self.__class__,
571
+ type=ActivityTypes.trace,
572
+ timestamp=datetime.now(timezone.utc),
573
+ from_property=SkipNone(
574
+ ChannelAccount.pick_properties(self.recipient, ["id", "name"])
575
+ ),
576
+ recipient=SkipNone(
577
+ ChannelAccount.pick_properties(self.from_property, ["id", "name"])
578
+ ),
579
+ reply_to_id=(
580
+ SkipNone(self.id) # preserve unset
581
+ if self.type != ActivityTypes.conversation_update
582
+ or self.channel_id not in ["directline", "webchat"]
583
+ else None
584
+ ),
585
+ service_url=self.service_url,
586
+ channel_id=self.channel_id,
587
+ conversation=SkipNone(
588
+ ConversationAccount.pick_properties(
589
+ self.conversation, ["is_group", "id", "name"]
590
+ )
591
+ ),
592
+ name=SkipNone(name),
593
+ label=SkipNone(label),
594
+ value_type=SkipNone(value_type),
595
+ value=SkipNone(value),
570
596
  ),
571
- reply_to_id=(
572
- SkipNone(self.id) # preserve unset
573
- if type != ActivityTypes.conversation_update
574
- or self.channel_id not in ["directline", "webchat"]
575
- else None
576
- ),
577
- service_url=self.service_url,
578
- channel_id=self.channel_id,
579
- conversation=SkipNone(
580
- ConversationAccount.pick_properties(
581
- self.conversation, ["is_group", "id", "name"]
582
- )
583
- ),
584
- name=SkipNone(name),
585
- label=SkipNone(label),
586
- value_type=SkipNone(value_type),
587
- value=SkipNone(value),
588
597
  ).as_trace_activity()
589
598
 
590
599
  @staticmethod
@@ -593,7 +602,7 @@ class Activity(AgentsModel, _ChannelIdFieldMixin):
593
602
  value: object = None,
594
603
  value_type: str | None = None,
595
604
  label: str | None = None,
596
- ):
605
+ ) -> Activity:
597
606
  """
598
607
  Creates an instance of the :class:`microsoft_agents.activity.Activity` class as a TraceActivity object.
599
608
 
@@ -607,13 +616,16 @@ class Activity(AgentsModel, _ChannelIdFieldMixin):
607
616
  if not value_type and value:
608
617
  value_type = type(value).__name__
609
618
 
610
- return pick_model(
619
+ return cast(
611
620
  Activity,
612
- type=ActivityTypes.trace,
613
- name=name,
614
- label=SkipNone(label),
615
- value_type=SkipNone(value_type),
616
- value=SkipNone(value),
621
+ pick_model(
622
+ Activity,
623
+ type=ActivityTypes.trace,
624
+ name=name,
625
+ label=SkipNone(label),
626
+ value_type=SkipNone(value_type),
627
+ value=SkipNone(value),
628
+ ),
617
629
  )
618
630
 
619
631
  @staticmethod
@@ -636,33 +648,71 @@ class Activity(AgentsModel, _ChannelIdFieldMixin):
636
648
  Composite values are split only on the first ``:``.
637
649
  :returns: A conversation reference for the conversation that contains this activity.
638
650
  """
639
- return pick_model(
651
+ return cast(
640
652
  ConversationReference,
641
- activity_id=(
642
- SkipNone(self.id)
643
- if self.type != ActivityTypes.conversation_update
644
- or self.channel_id not in ["directline", "webchat"]
645
- else None
646
- ),
647
- user=copy(self.from_property),
648
- agent=copy(self.recipient),
649
- conversation=copy(self.conversation),
650
- channel_id=(
651
- self.channel_id.split(":", 1)[0]
652
- if force_base_channel and self.channel_id is not None
653
- else self.channel_id
653
+ pick_model(
654
+ ConversationReference,
655
+ activity_id=(
656
+ SkipNone(self.id)
657
+ if self.type != ActivityTypes.conversation_update
658
+ or self.channel_id not in ["directline", "webchat"]
659
+ else None
660
+ ),
661
+ user=copy(self.from_property),
662
+ agent=copy(self.recipient),
663
+ conversation=copy(self.conversation),
664
+ channel_id=(
665
+ self.channel_id.split(":", 1)[0]
666
+ if force_base_channel and self.channel_id is not None
667
+ else self.channel_id
668
+ ),
669
+ locale=self.locale,
670
+ service_url=self.service_url,
654
671
  ),
655
- locale=self.locale,
656
- service_url=self.service_url,
657
672
  )
658
673
 
674
+ @staticmethod
675
+ def _convert_entity(raw_entity: Entity, entity_cls: type[_EntityT]) -> _EntityT:
676
+ """
677
+ Converts an entity to a specific entity type.
678
+
679
+ :param raw_entity: The entity to convert.
680
+ :param entity_cls: The class of the entity type to convert to.
681
+ :return: The converted entity of the specified type.
682
+ """
683
+ if isinstance(raw_entity, entity_cls):
684
+ return raw_entity
685
+ return entity_cls.model_validate(raw_entity.model_dump())
686
+
687
+ @staticmethod
688
+ def _convert_entity_list(
689
+ raw_entities: list[Entity], entity_cls: type[_EntityT]
690
+ ) -> list[_EntityT]:
691
+ """
692
+ Converts a list of entities to a list of a specific entity type.
693
+
694
+ :param raw_entities: The list of entities to convert.
695
+ :param entity_cls: The class of the entity type to convert to.
696
+ :return: The list of converted entities of the specified type.
697
+ """
698
+
699
+ entities: list[_EntityT] = []
700
+ for e in raw_entities:
701
+ entities.append(Activity._convert_entity(e, entity_cls))
702
+ return entities
703
+
659
704
  def get_product_info_entity(self) -> Optional[ProductInfo]:
660
705
  if not self.entities:
661
706
  return None
662
707
  target = EntityTypes.PRODUCT_INFO.lower()
663
708
  # validated entities can be Entity, and that prevents us from
664
709
  # making assumptions about the casing of the 'type' attribute
665
- return next(filter(lambda e: e.type.lower() == target, self.entities), None)
710
+ raw_product_info = next(
711
+ filter(lambda e: e.type.lower() == target, self.entities), None
712
+ )
713
+ if raw_product_info is None:
714
+ return None
715
+ return Activity._convert_entity(raw_product_info, ProductInfo)
666
716
 
667
717
  def get_mentions(self) -> list[Mention]:
668
718
  """
@@ -676,7 +726,11 @@ class Activity(AgentsModel, _ChannelIdFieldMixin):
676
726
  """
677
727
  if not self.entities:
678
728
  return []
679
- return [x for x in self.entities if x.type.lower() == EntityTypes.MENTION]
729
+ raw_mentions = [
730
+ x for x in self.entities if x.type.lower() == EntityTypes.MENTION
731
+ ]
732
+
733
+ return Activity._convert_entity_list(raw_mentions, Mention)
680
734
 
681
735
  def get_reply_conversation_reference(
682
736
  self, reply: ResourceResponse
@@ -20,6 +20,44 @@ _NAME_TO_LEVEL = {
20
20
  }
21
21
 
22
22
 
23
+ class ColorFormatter(logging.Formatter):
24
+ """Logging formatter that can add ANSI color based on severity."""
25
+
26
+ MAGENTA = "\033[1;35m"
27
+ RED = "\033[1;31m"
28
+ YELLOW = "\033[1;33m"
29
+ GREEN = "\033[1;32m"
30
+ BLUE = "\033[1;34m"
31
+ RESET = "\033[0m"
32
+
33
+ FMT = (
34
+ "%(asctime)s - %(name)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)"
35
+ )
36
+
37
+ def __init__(
38
+ self,
39
+ use_colors: bool = True,
40
+ ) -> None:
41
+ super().__init__(fmt=self.FMT)
42
+ self._plain_formatter = logging.Formatter(fmt=self.FMT)
43
+ if not use_colors:
44
+ self._formatters: dict[int, logging.Formatter] = {}
45
+ return
46
+ self._formatters = {
47
+ logging.CRITICAL: logging.Formatter(
48
+ f"{self.MAGENTA}{self.FMT}{self.RESET}"
49
+ ),
50
+ logging.ERROR: logging.Formatter(f"{self.RED}{self.FMT}{self.RESET}"),
51
+ logging.WARNING: logging.Formatter(f"{self.YELLOW}{self.FMT}{self.RESET}"),
52
+ logging.INFO: logging.Formatter(f"{self.GREEN}{self.FMT}{self.RESET}"),
53
+ logging.DEBUG: logging.Formatter(f"{self.BLUE}{self.FMT}{self.RESET}"),
54
+ }
55
+
56
+ def format(self, record: logging.LogRecord) -> str:
57
+ formatter = self._formatters.get(record.levelno)
58
+ return (formatter or self._plain_formatter).format(record)
59
+
60
+
23
61
  def _configure_logging(logging_config: dict):
24
62
  """Configures logging based on the provided logging configuration dictionary.
25
63
 
@@ -31,9 +69,7 @@ def _configure_logging(logging_config: dict):
31
69
 
32
70
  console_handler = logging.StreamHandler()
33
71
  console_handler.setFormatter(
34
- logging.Formatter(
35
- "%(asctime)s - %(name)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)"
36
- )
72
+ ColorFormatter(use_colors=console_handler.stream.isatty())
37
73
  )
38
74
 
39
75
  for key in log_levels:
@@ -50,6 +86,7 @@ def _configure_logging(logging_config: dict):
50
86
  else:
51
87
  logger = logging.getLogger(namespace)
52
88
 
89
+ logger.propagate = False # Prevent log messages from being propagated
53
90
  logger.handlers.clear() # Remove existing handlers to prevent duplicates
54
91
  logger.addHandler(console_handler)
55
92
  logger.setLevel(level)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: microsoft-agents-activity
3
- Version: 1.2.0.dev4
3
+ Version: 1.2.0.dev7
4
4
  Summary: A protocol library for Microsoft Agents
5
5
  Author: Microsoft Corporation
6
6
  License-Expression: MIT
@@ -3,7 +3,7 @@ microsoft_agents/activity/_channel_id_field_mixin.py,sha256=YRgxAiDFp8kYBVaOBUJG
3
3
  microsoft_agents/activity/_model_utils.py,sha256=gUAKCOl98jA9tt95o5G7LoMuHgROvrW6lY9s0CzQu6k,2004
4
4
  microsoft_agents/activity/_type_aliases.py,sha256=kP5tYxEKSgcgBT3bucdluVVxxopwrUaGqKL7X_TQ0Og,229
5
5
  microsoft_agents/activity/action_types.py,sha256=nslx3hvQAd120NPdXZXdprwdjj3I3d4MWXV1b-Igr_o,419
6
- microsoft_agents/activity/activity.py,sha256=WM_965Nq_C6TR8rOFALTnY3DJh5ab6PMz0eeV_0LvJM,33759
6
+ microsoft_agents/activity/activity.py,sha256=PvWraC7qkovJO9C-Guu90I2T7uAo7mJ5oyuB3e-o5pE,35811
7
7
  microsoft_agents/activity/activity_event_names.py,sha256=dmtE1w50nnemTC_s1QG7IqTbnocpiczK_CG31JpgoJ4,254
8
8
  microsoft_agents/activity/activity_importance.py,sha256=cRIlyz8EjmztPmfcgdqZ6Csp6Vp2rQhXt4ZWIDXdt_s,212
9
9
  microsoft_agents/activity/activity_types.py,sha256=dm4vTVSoa-8_aPY_kZuU6C_FWQAURgZRassR1o8o9qQ,762
@@ -82,7 +82,7 @@ microsoft_agents/activity/video_card.py,sha256=6ZYvTYCsIsACbR3KJBhfwFJ1GTYbjKJWv
82
82
  microsoft_agents/activity/_utils/__init__.py,sha256=GM-i-Cw49__7zSb7OMzS5S4KFkKU1uEf5LlINUgipmY,172
83
83
  microsoft_agents/activity/_utils/_deferred_string.py,sha256=4FGBIExyfmncr-jxgsARWct9cy-5nnPll2nUD-UYkXw,1124
84
84
  microsoft_agents/activity/config/__init__.py,sha256=HhYU_mkuQwpZS87JEsYmPzcWQHVRqEAdIzkKQZyvzr4,206
85
- microsoft_agents/activity/config/_configure_logging.py,sha256=qD_Np4Qqsm9AVGDXhCs4JCH2LCqWs5XIiexZKShPGag,1704
85
+ microsoft_agents/activity/config/_configure_logging.py,sha256=oZOC9c09dNmscXzvLflLGqn950WdkvfIiVtWU173dcc,3063
86
86
  microsoft_agents/activity/config/_load_configuration.py,sha256=u_Nrt20ZZtcONpC26jjcIJI1zGMC85oEHGVw_TmzLl0,1228
87
87
  microsoft_agents/activity/entity/__init__.py,sha256=PGs4ZT-NclfuZbEncznR7hDxH7XROlpmL2Pw9nK_tcA,880
88
88
  microsoft_agents/activity/entity/_schema_mixin.py,sha256=qL9uPDdxyq9Kwa8UscjxMDEQWytfmaUl6qa7Hym6nOE,1907
@@ -205,8 +205,8 @@ microsoft_agents/activity/teams/teams_member.py,sha256=vRcLRHy6wTfH7DP6k6QbrKkOR
205
205
  microsoft_agents/activity/teams/teams_paged_members_result.py,sha256=jTOhoFbKPK23i-zufimB0d0_m-Lud5jQCcCHsqk2tgk,531
206
206
  microsoft_agents/activity/teams/tenant_info.py,sha256=h8OxMd6LD5lWVj_LK2ut8z7SB0I7Gx74W1wwEiYOzXk,296
207
207
  microsoft_agents/activity/teams/user_meeting_details.py,sha256=tvk2CWYf7Bo-DhK8NSojhanJDXEOGVrKXxJDca03CAo,467
208
- microsoft_agents_activity-1.2.0.dev4.dist-info/licenses/LICENSE,sha256=ws_MuBL-SCEBqPBFl9_FqZkaaydIJmxHrJG2parhU4M,1141
209
- microsoft_agents_activity-1.2.0.dev4.dist-info/METADATA,sha256=1AAZNOwWkfM8q-9fZtmgSAgA1j9qSldejNvEXMzlkbg,10859
210
- microsoft_agents_activity-1.2.0.dev4.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
211
- microsoft_agents_activity-1.2.0.dev4.dist-info/top_level.txt,sha256=lWKcT4v6fTA_NgsuHdNvuMjSrkiBMXohn64ApY7Xi8A,17
212
- microsoft_agents_activity-1.2.0.dev4.dist-info/RECORD,,
208
+ microsoft_agents_activity-1.2.0.dev7.dist-info/licenses/LICENSE,sha256=ws_MuBL-SCEBqPBFl9_FqZkaaydIJmxHrJG2parhU4M,1141
209
+ microsoft_agents_activity-1.2.0.dev7.dist-info/METADATA,sha256=z4ZYg6Vu-Zz-eqnt8tM0juwhdpaXlrYUuxG7oETtjso,10859
210
+ microsoft_agents_activity-1.2.0.dev7.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
211
+ microsoft_agents_activity-1.2.0.dev7.dist-info/top_level.txt,sha256=lWKcT4v6fTA_NgsuHdNvuMjSrkiBMXohn64ApY7Xi8A,17
212
+ microsoft_agents_activity-1.2.0.dev7.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (82.0.1)
2
+ Generator: setuptools (83.0.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5