microsoft-agents-hosting-dialogs 0.10.0.dev2__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.
Files changed (91) hide show
  1. microsoft_agents/hosting/dialogs/__init__.py +76 -0
  2. microsoft_agents/hosting/dialogs/_component_registration.py +30 -0
  3. microsoft_agents/hosting/dialogs/_telemetry_client.py +78 -0
  4. microsoft_agents/hosting/dialogs/choices/__init__.py +38 -0
  5. microsoft_agents/hosting/dialogs/choices/channel.py +121 -0
  6. microsoft_agents/hosting/dialogs/choices/choice_factory.py +262 -0
  7. microsoft_agents/hosting/dialogs/choices/choice_recognizer.py +148 -0
  8. microsoft_agents/hosting/dialogs/choices/find.py +242 -0
  9. microsoft_agents/hosting/dialogs/choices/models/__init__.py +23 -0
  10. microsoft_agents/hosting/dialogs/choices/models/choice.py +14 -0
  11. microsoft_agents/hosting/dialogs/choices/models/choice_factory_options.py +13 -0
  12. microsoft_agents/hosting/dialogs/choices/models/find_choices_options.py +28 -0
  13. microsoft_agents/hosting/dialogs/choices/models/find_values_options.py +31 -0
  14. microsoft_agents/hosting/dialogs/choices/models/found_choice.py +22 -0
  15. microsoft_agents/hosting/dialogs/choices/models/found_value.py +20 -0
  16. microsoft_agents/hosting/dialogs/choices/models/list_style.py +15 -0
  17. microsoft_agents/hosting/dialogs/choices/models/model_result.py +16 -0
  18. microsoft_agents/hosting/dialogs/choices/models/sorted_value.py +16 -0
  19. microsoft_agents/hosting/dialogs/choices/models/token.py +20 -0
  20. microsoft_agents/hosting/dialogs/choices/tokenizer.py +92 -0
  21. microsoft_agents/hosting/dialogs/component_dialog.py +284 -0
  22. microsoft_agents/hosting/dialogs/dialog.py +198 -0
  23. microsoft_agents/hosting/dialogs/dialog_component_registration.py +52 -0
  24. microsoft_agents/hosting/dialogs/dialog_container.py +31 -0
  25. microsoft_agents/hosting/dialogs/dialog_context.py +426 -0
  26. microsoft_agents/hosting/dialogs/dialog_extensions.py +201 -0
  27. microsoft_agents/hosting/dialogs/dialog_manager.py +189 -0
  28. microsoft_agents/hosting/dialogs/dialog_manager_result.py +17 -0
  29. microsoft_agents/hosting/dialogs/dialog_set.py +174 -0
  30. microsoft_agents/hosting/dialogs/dialog_state.py +20 -0
  31. microsoft_agents/hosting/dialogs/memory/__init__.py +24 -0
  32. microsoft_agents/hosting/dialogs/memory/component_memory_scopes_base.py +14 -0
  33. microsoft_agents/hosting/dialogs/memory/component_path_resolvers_base.py +15 -0
  34. microsoft_agents/hosting/dialogs/memory/dialog_path.py +33 -0
  35. microsoft_agents/hosting/dialogs/memory/dialog_state_manager.py +563 -0
  36. microsoft_agents/hosting/dialogs/memory/dialog_state_manager_configuration.py +11 -0
  37. microsoft_agents/hosting/dialogs/memory/path_resolver_base.py +8 -0
  38. microsoft_agents/hosting/dialogs/memory/path_resolvers/__init__.py +19 -0
  39. microsoft_agents/hosting/dialogs/memory/path_resolvers/alias_path_resolver.py +53 -0
  40. microsoft_agents/hosting/dialogs/memory/path_resolvers/at_at_path_resolver.py +9 -0
  41. microsoft_agents/hosting/dialogs/memory/path_resolvers/at_path_resolver.py +44 -0
  42. microsoft_agents/hosting/dialogs/memory/path_resolvers/dollar_path_resolver.py +9 -0
  43. microsoft_agents/hosting/dialogs/memory/path_resolvers/hash_path_resolver.py +9 -0
  44. microsoft_agents/hosting/dialogs/memory/path_resolvers/percent_path_resolver.py +9 -0
  45. microsoft_agents/hosting/dialogs/memory/scope_path.py +38 -0
  46. microsoft_agents/hosting/dialogs/memory/scopes/__init__.py +31 -0
  47. microsoft_agents/hosting/dialogs/memory/scopes/bot_state_memory_scope.py +66 -0
  48. microsoft_agents/hosting/dialogs/memory/scopes/class_memory_scope.py +64 -0
  49. microsoft_agents/hosting/dialogs/memory/scopes/conversation_memory_scope.py +12 -0
  50. microsoft_agents/hosting/dialogs/memory/scopes/dialog_class_memory_scope.py +52 -0
  51. microsoft_agents/hosting/dialogs/memory/scopes/dialog_context_memory_scope.py +68 -0
  52. microsoft_agents/hosting/dialogs/memory/scopes/dialog_memory_scope.py +75 -0
  53. microsoft_agents/hosting/dialogs/memory/scopes/memory_scope.py +91 -0
  54. microsoft_agents/hosting/dialogs/memory/scopes/settings_memory_scope.py +38 -0
  55. microsoft_agents/hosting/dialogs/memory/scopes/this_memory_scope.py +36 -0
  56. microsoft_agents/hosting/dialogs/memory/scopes/turn_memory_scope.py +86 -0
  57. microsoft_agents/hosting/dialogs/memory/scopes/user_memory_scope.py +12 -0
  58. microsoft_agents/hosting/dialogs/models/__init__.py +15 -0
  59. microsoft_agents/hosting/dialogs/models/dialog_event.py +13 -0
  60. microsoft_agents/hosting/dialogs/models/dialog_events.py +12 -0
  61. microsoft_agents/hosting/dialogs/models/dialog_instance.py +28 -0
  62. microsoft_agents/hosting/dialogs/models/dialog_reason.py +34 -0
  63. microsoft_agents/hosting/dialogs/models/dialog_turn_result.py +17 -0
  64. microsoft_agents/hosting/dialogs/models/dialog_turn_status.py +26 -0
  65. microsoft_agents/hosting/dialogs/object_path.py +315 -0
  66. microsoft_agents/hosting/dialogs/persisted_state.py +22 -0
  67. microsoft_agents/hosting/dialogs/persisted_state_keys.py +8 -0
  68. microsoft_agents/hosting/dialogs/prompts/__init__.py +41 -0
  69. microsoft_agents/hosting/dialogs/prompts/activity_prompt.py +203 -0
  70. microsoft_agents/hosting/dialogs/prompts/attachment_prompt.py +87 -0
  71. microsoft_agents/hosting/dialogs/prompts/choice_prompt.py +156 -0
  72. microsoft_agents/hosting/dialogs/prompts/confirm_prompt.py +161 -0
  73. microsoft_agents/hosting/dialogs/prompts/datetime_prompt.py +90 -0
  74. microsoft_agents/hosting/dialogs/prompts/datetime_resolution.py +16 -0
  75. microsoft_agents/hosting/dialogs/prompts/number_prompt.py +81 -0
  76. microsoft_agents/hosting/dialogs/prompts/oauth_prompt.py +569 -0
  77. microsoft_agents/hosting/dialogs/prompts/oauth_prompt_settings.py +43 -0
  78. microsoft_agents/hosting/dialogs/prompts/prompt.py +224 -0
  79. microsoft_agents/hosting/dialogs/prompts/prompt_culture_models.py +222 -0
  80. microsoft_agents/hosting/dialogs/prompts/prompt_options.py +42 -0
  81. microsoft_agents/hosting/dialogs/prompts/prompt_recognizer_result.py +11 -0
  82. microsoft_agents/hosting/dialogs/prompts/prompt_validator.py +0 -0
  83. microsoft_agents/hosting/dialogs/prompts/prompt_validator_context.py +44 -0
  84. microsoft_agents/hosting/dialogs/prompts/text_prompt.py +82 -0
  85. microsoft_agents/hosting/dialogs/waterfall_dialog.py +266 -0
  86. microsoft_agents/hosting/dialogs/waterfall_step_context.py +109 -0
  87. microsoft_agents_hosting_dialogs-0.10.0.dev2.dist-info/METADATA +87 -0
  88. microsoft_agents_hosting_dialogs-0.10.0.dev2.dist-info/RECORD +91 -0
  89. microsoft_agents_hosting_dialogs-0.10.0.dev2.dist-info/WHEEL +5 -0
  90. microsoft_agents_hosting_dialogs-0.10.0.dev2.dist-info/licenses/LICENSE +21 -0
  91. microsoft_agents_hosting_dialogs-0.10.0.dev2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,76 @@
1
+ # coding=utf-8
2
+ # --------------------------------------------------------------------------
3
+ # Copyright (c) Microsoft Corporation. All rights reserved.
4
+ # Licensed under the MIT License. See License.txt in the project root for
5
+ # license information.
6
+ # --------------------------------------------------------------------------
7
+
8
+ __version__ = "0.0.0"
9
+
10
+ from .component_dialog import ComponentDialog
11
+ from .dialog_container import DialogContainer
12
+ from .dialog_context import DialogContext
13
+ from .models.dialog_event import DialogEvent
14
+ from .models.dialog_events import DialogEvents
15
+ from .models.dialog_instance import DialogInstance
16
+ from .models.dialog_reason import DialogReason
17
+ from .dialog_set import DialogSet
18
+ from .dialog_state import DialogState
19
+ from .models.dialog_turn_result import DialogTurnResult
20
+ from .models.dialog_turn_status import DialogTurnStatus
21
+ from .dialog_manager import DialogManager
22
+ from .dialog_manager_result import DialogManagerResult
23
+ from .dialog import Dialog
24
+ from .dialog_component_registration import DialogsComponentRegistration
25
+ from .persisted_state_keys import PersistedStateKeys
26
+ from .persisted_state import PersistedState
27
+ from .waterfall_dialog import WaterfallDialog
28
+ from .waterfall_step_context import WaterfallStepContext
29
+ from .dialog_extensions import DialogExtensions
30
+ from .prompts import *
31
+ from .choices import *
32
+ from .object_path import ObjectPath
33
+ from .models import (
34
+ DialogEvent,
35
+ DialogEvents,
36
+ DialogInstance,
37
+ DialogReason,
38
+ DialogTurnResult,
39
+ DialogTurnStatus,
40
+ )
41
+
42
+ __all__ = [
43
+ "ComponentDialog",
44
+ "DialogContainer",
45
+ "DialogContext",
46
+ "DialogEvent",
47
+ "DialogEvents",
48
+ "DialogInstance",
49
+ "DialogReason",
50
+ "DialogSet",
51
+ "DialogState",
52
+ "DialogTurnResult",
53
+ "DialogTurnStatus",
54
+ "DialogManager",
55
+ "DialogManagerResult",
56
+ "Dialog",
57
+ "DialogsComponentRegistration",
58
+ "WaterfallDialog",
59
+ "WaterfallStepContext",
60
+ "ConfirmPrompt",
61
+ "DateTimePrompt",
62
+ "DateTimeResolution",
63
+ "NumberPrompt",
64
+ "OAuthPrompt",
65
+ "OAuthPromptSettings",
66
+ "PersistedStateKeys",
67
+ "PersistedState",
68
+ "PromptRecognizerResult",
69
+ "PromptValidatorContext",
70
+ "Prompt",
71
+ "PromptOptions",
72
+ "TextPrompt",
73
+ "DialogExtensions",
74
+ "ObjectPath",
75
+ "__version__",
76
+ ]
@@ -0,0 +1,30 @@
1
+ # Copyright (c) Microsoft Corporation. All rights reserved.
2
+ # Licensed under the MIT License.
3
+
4
+ from typing import List
5
+
6
+
7
+ class ComponentRegistration:
8
+ """
9
+ Simple component registration that allows dialogs and other components
10
+ to register memory scopes and path resolvers.
11
+ """
12
+
13
+ _components: List = []
14
+
15
+ @classmethod
16
+ def add(cls, component: object) -> None:
17
+ """
18
+ Register a component. Duplicate types are ignored.
19
+ :param component: The component instance to register.
20
+ """
21
+ if not any(type(c) == type(component) for c in cls._components):
22
+ cls._components.append(component)
23
+
24
+ @classmethod
25
+ def get_components(cls) -> List:
26
+ """
27
+ Gets all registered components.
28
+ :return: List of registered component instances.
29
+ """
30
+ return list(cls._components)
@@ -0,0 +1,78 @@
1
+ # Copyright (c) Microsoft Corporation. All rights reserved.
2
+ # Licensed under the MIT License.
3
+
4
+
5
+ class AgentTelemetryClient:
6
+ """
7
+ Interface for telemetry logging. Override to send telemetry to a custom sink.
8
+ """
9
+
10
+ def track_event(
11
+ self,
12
+ name: str,
13
+ properties: dict[str, str] | None = None,
14
+ metrics: dict[str, float] | None = None,
15
+ ) -> None:
16
+ pass
17
+
18
+ def track_exception(
19
+ self,
20
+ exception: Exception,
21
+ properties: dict[str, str] | None = None,
22
+ measurements: dict[str, float] | None = None,
23
+ ) -> None:
24
+ pass
25
+
26
+ def track_dependency(
27
+ self,
28
+ name: str,
29
+ data: str | None = None,
30
+ type_name: str | None = None,
31
+ target: str | None = None,
32
+ duration: int | None = None,
33
+ success: bool = True,
34
+ result_code: str | None = None,
35
+ properties: dict[str, str] | None = None,
36
+ ) -> None:
37
+ pass
38
+
39
+ def flush(self) -> None:
40
+ pass
41
+
42
+
43
+ class NullTelemetryClient(AgentTelemetryClient):
44
+ """
45
+ No-op telemetry client. All calls are silently discarded.
46
+ """
47
+
48
+ def track_event(
49
+ self,
50
+ name: str,
51
+ properties: dict[str, str] | None = None,
52
+ metrics: dict[str, float] | None = None,
53
+ ) -> None:
54
+ pass
55
+
56
+ def track_exception(
57
+ self,
58
+ exception: Exception,
59
+ properties: dict[str, str] | None = None,
60
+ measurements: dict[str, float] | None = None,
61
+ ) -> None:
62
+ pass
63
+
64
+ def track_dependency(
65
+ self,
66
+ name: str,
67
+ data: str | None = None,
68
+ type_name: str | None = None,
69
+ target: str | None = None,
70
+ duration: int | None = None,
71
+ success: bool = True,
72
+ result_code: str | None = None,
73
+ properties: dict[str, str] | None = None,
74
+ ) -> None:
75
+ pass
76
+
77
+ def flush(self) -> None:
78
+ pass
@@ -0,0 +1,38 @@
1
+ # coding=utf-8
2
+ # --------------------------------------------------------------------------
3
+ # Copyright (c) Microsoft Corporation. All rights reserved.
4
+ # Licensed under the MIT License. See License.txt in the project root for
5
+ # license information.
6
+ # --------------------------------------------------------------------------
7
+
8
+ from .channel import Channel
9
+ from .models.choice import Choice
10
+ from .models.choice_factory_options import ChoiceFactoryOptions
11
+ from .choice_factory import ChoiceFactory
12
+ from .choice_recognizer import ChoiceRecognizers
13
+ from .find import Find
14
+ from .models.find_choices_options import FindChoicesOptions, FindValuesOptions
15
+ from .models.found_choice import FoundChoice
16
+ from .models.found_value import FoundValue
17
+ from .models.list_style import ListStyle
18
+ from .models.model_result import ModelResult
19
+ from .models.sorted_value import SortedValue
20
+ from .models.token import Token
21
+ from .tokenizer import Tokenizer
22
+
23
+ __all__ = [
24
+ "Channel",
25
+ "Choice",
26
+ "ChoiceFactory",
27
+ "ChoiceFactoryOptions",
28
+ "ChoiceRecognizers",
29
+ "Find",
30
+ "FindChoicesOptions",
31
+ "FindValuesOptions",
32
+ "FoundChoice",
33
+ "ListStyle",
34
+ "ModelResult",
35
+ "SortedValue",
36
+ "Token",
37
+ "Tokenizer",
38
+ ]
@@ -0,0 +1,121 @@
1
+ # Copyright (c) Microsoft Corporation. All rights reserved.
2
+ # Licensed under the MIT License.
3
+
4
+ from microsoft_agents.hosting.core import TurnContext
5
+ from microsoft_agents.activity import Channels
6
+
7
+
8
+ class Channel:
9
+ """
10
+ Methods for determining channel-specific functionality.
11
+ """
12
+
13
+ @staticmethod
14
+ def supports_suggested_actions(channel_id: str, button_cnt: int = 100) -> bool:
15
+ """Determine if a number of Suggested Actions are supported by a Channel.
16
+
17
+ Args:
18
+ channel_id (str): The Channel to check the if Suggested Actions are supported in.
19
+ button_cnt (int, optional): Defaults to 100. The number of Suggested Actions to check for the Channel.
20
+
21
+ Returns:
22
+ bool: True if the Channel supports the button_cnt total Suggested Actions, False if the Channel does not
23
+ support that number of Suggested Actions.
24
+ """
25
+ if isinstance(channel_id, Channels):
26
+ channel_id = channel_id.value
27
+
28
+ max_actions = {
29
+ # https://developers.facebook.com/docs/messenger-platform/send-messages/quick-replies
30
+ Channels.facebook.value: 10,
31
+ Channels.skype.value: 10,
32
+ # https://developers.line.biz/en/reference/messaging-api/#items-object
33
+ Channels.line.value: 13,
34
+ # https://dev.kik.com/#/docs/messaging#text-response-object
35
+ Channels.kik.value: 20,
36
+ Channels.telegram.value: 100,
37
+ Channels.emulator.value: 100,
38
+ Channels.direct_line.value: 100,
39
+ Channels.direct_line_speech.value: 100,
40
+ Channels.webchat.value: 100,
41
+ }
42
+ return (
43
+ button_cnt <= max_actions[channel_id]
44
+ if channel_id in max_actions
45
+ else False
46
+ )
47
+
48
+ @staticmethod
49
+ def supports_card_actions(channel_id: str, button_cnt: int = 100) -> bool:
50
+ """Determine if a number of Card Actions are supported by a Channel.
51
+
52
+ Args:
53
+ channel_id (str): The Channel to check if the Card Actions are supported in.
54
+ button_cnt (int, optional): Defaults to 100. The number of Card Actions to check for the Channel.
55
+
56
+ Returns:
57
+ bool: True if the Channel supports the button_cnt total Card Actions, False if the Channel does not support
58
+ that number of Card Actions.
59
+ """
60
+ if isinstance(channel_id, Channels):
61
+ channel_id = channel_id.value
62
+
63
+ max_actions = {
64
+ Channels.facebook.value: 3,
65
+ Channels.skype.value: 3,
66
+ Channels.ms_teams.value: 3,
67
+ Channels.line.value: 99,
68
+ Channels.slack.value: 100,
69
+ Channels.telegram.value: 100,
70
+ Channels.emulator.value: 100,
71
+ Channels.direct_line.value: 100,
72
+ Channels.direct_line_speech.value: 100,
73
+ Channels.webchat.value: 100,
74
+ }
75
+ return (
76
+ button_cnt <= max_actions[channel_id]
77
+ if channel_id in max_actions
78
+ else False
79
+ )
80
+
81
+ @staticmethod
82
+ def has_message_feed(_: str) -> bool:
83
+ """Determine if a Channel has a Message Feed.
84
+
85
+ Args:
86
+ channel_id (str): The Channel to check for Message Feed.
87
+
88
+ Returns:
89
+ bool: True if the Channel has a Message Feed, False if it does not.
90
+ """
91
+
92
+ return True
93
+
94
+ @staticmethod
95
+ def get_channel_id(turn_context: TurnContext) -> str:
96
+ """Get the channel ID from the TurnContext's activity.
97
+
98
+ Args:
99
+ turn_context (TurnContext): The current turn context.
100
+
101
+ Returns:
102
+ str: The channel ID, or an empty string if not set.
103
+ """
104
+ if turn_context.activity and turn_context.activity.channel_id:
105
+ return turn_context.activity.channel_id
106
+ return ""
107
+
108
+ @staticmethod
109
+ def max_action_title_length( # pylint: disable=unused-argument
110
+ channel_id: str,
111
+ ) -> int:
112
+ """Maximum length allowed for Action Titles.
113
+
114
+ Args:
115
+ channel_id (str): The Channel to determine Maximum Action Title Length.
116
+
117
+ Returns:
118
+ int: The total number of characters allowed for an Action Title on a specific Channel.
119
+ """
120
+
121
+ return 20
@@ -0,0 +1,262 @@
1
+ # Copyright (c) Microsoft Corporation. All rights reserved.
2
+ # Licensed under the MIT License.
3
+
4
+ from collections.abc import Iterable
5
+
6
+ from microsoft_agents.hosting.core import CardFactory, MessageFactory
7
+ from microsoft_agents.activity import (
8
+ ActionTypes,
9
+ Activity,
10
+ CardAction,
11
+ HeroCard,
12
+ InputHints,
13
+ )
14
+
15
+ from . import Channel, Choice, ChoiceFactoryOptions
16
+
17
+
18
+ class ChoiceFactory:
19
+ """
20
+ Assists with formatting a message activity that contains a list of choices.
21
+ """
22
+
23
+ @staticmethod
24
+ def for_channel(
25
+ channel_id: str,
26
+ choices: Iterable[str | Choice],
27
+ text: str | None = None,
28
+ speak: str | None = None,
29
+ options: ChoiceFactoryOptions | None = None,
30
+ ) -> Activity:
31
+ """
32
+ Creates a message activity that includes a list of choices formatted based on the
33
+ capabilities of a given channel.
34
+
35
+ Parameters
36
+ ----------
37
+ channel_id: A channel ID.
38
+ choices: List of choices to render
39
+ text: (Optional) Text of the message to send.
40
+ speak (Optional) SSML. Text to be spoken by your bot on a speech-enabled channel.
41
+ """
42
+ if channel_id is None:
43
+ channel_id = ""
44
+
45
+ choice_list: list[Choice] = ChoiceFactory._to_choices(choices)
46
+
47
+ # Find maximum title length
48
+ max_title_length = 0
49
+ for choice in choice_list:
50
+ if choice.action is not None and choice.action.title not in (None, ""):
51
+ size = len(choice.action.title)
52
+ else:
53
+ size = len(choice.value) if choice.value is not None else 0
54
+
55
+ max_title_length = max(max_title_length, size)
56
+
57
+ # Determine list style
58
+ supports_suggested_actions = Channel.supports_suggested_actions(
59
+ channel_id, len(choice_list)
60
+ )
61
+ supports_card_actions = Channel.supports_card_actions(
62
+ channel_id, len(choice_list)
63
+ )
64
+ max_action_title_length = Channel.max_action_title_length(channel_id)
65
+ long_titles = max_title_length > max_action_title_length
66
+
67
+ if not long_titles and not supports_suggested_actions and supports_card_actions:
68
+ # SuggestedActions is the preferred approach, but for channels that don't
69
+ # support them (e.g. Teams, Cortana) we should use a HeroCard with CardActions
70
+ return ChoiceFactory.hero_card(choice_list, text, speak)
71
+ if not long_titles and supports_suggested_actions:
72
+ # We always prefer showing choices using suggested actions. If the titles are too long, however,
73
+ # we'll have to show them as a text list.
74
+ return ChoiceFactory.suggested_action(choice_list, text, speak)
75
+ if not long_titles and len(choice_list) <= 3:
76
+ # If the titles are short and there are 3 or less choices we'll use an inline list.
77
+ return ChoiceFactory.inline(choice_list, text, speak, options)
78
+ # Show a numbered list.
79
+ return ChoiceFactory.list_style(choice_list, text, speak, options)
80
+
81
+ @staticmethod
82
+ def inline(
83
+ choices: Iterable[str | Choice],
84
+ text: str | None = None,
85
+ speak: str | None = None,
86
+ options: ChoiceFactoryOptions | None = None,
87
+ ) -> Activity:
88
+ """
89
+ Creates a message activity that includes a list of choices formatted as an inline list.
90
+
91
+ Parameters
92
+ ----------
93
+ choices: The list of choices to render.
94
+ text: (Optional) The text of the message to send.
95
+ speak: (Optional) SSML. Text to be spoken by your bot on a speech-enabled channel.
96
+ options: (Optional) The formatting options to use to tweak rendering of list.
97
+ """
98
+ choice_list = ChoiceFactory._to_choices(choices)
99
+
100
+ if options is None:
101
+ options = ChoiceFactoryOptions()
102
+
103
+ opt = ChoiceFactoryOptions(
104
+ inline_separator=options.inline_separator or ", ",
105
+ inline_or=options.inline_or or " or ",
106
+ inline_or_more=options.inline_or_more or ", or ",
107
+ include_numbers=(
108
+ options.include_numbers if options.include_numbers is not None else True
109
+ ),
110
+ )
111
+
112
+ # Format list of choices
113
+ connector = ""
114
+ txt_builder: list[str] = []
115
+ if text is not None:
116
+ txt_builder.append(text)
117
+ txt_builder.append(" ")
118
+
119
+ for index, choice in enumerate(choice_list):
120
+ title = (
121
+ choice.action.title
122
+ if (choice.action is not None and choice.action.title is not None)
123
+ else choice.value
124
+ )
125
+ txt_builder.append(connector)
126
+ if opt.include_numbers is True:
127
+ txt_builder.append("(")
128
+ txt_builder.append(f"{index + 1}")
129
+ txt_builder.append(") ")
130
+
131
+ txt_builder.append(title or "title")
132
+ if index == (len(choice_list) - 2):
133
+ connector = opt.inline_or if index == 0 else opt.inline_or_more
134
+ connector = connector or ""
135
+ else:
136
+ connector = opt.inline_separator or ""
137
+
138
+ # Return activity with choices as an inline list.
139
+ return MessageFactory.text(
140
+ "".join(txt_builder), speak, InputHints.expecting_input
141
+ )
142
+
143
+ @staticmethod
144
+ def list_style(
145
+ choices: Iterable[str | Choice],
146
+ text: str | None = None,
147
+ speak: str | None = None,
148
+ options: ChoiceFactoryOptions | None = None,
149
+ ) -> Activity:
150
+ """
151
+ Creates a message activity that includes a list of choices formatted as a numbered or bulleted list.
152
+
153
+ Parameters
154
+ ----------
155
+
156
+ choices: The list of choices to render.
157
+
158
+ text: (Optional) The text of the message to send.
159
+
160
+ speak: (Optional) SSML. Text to be spoken by your bot on a speech-enabled channel.
161
+
162
+ options: (Optional) The formatting options to use to tweak rendering of list.
163
+ """
164
+ choices = ChoiceFactory._to_choices(choices)
165
+ if options is None:
166
+ options = ChoiceFactoryOptions()
167
+
168
+ if options.include_numbers is None:
169
+ include_numbers = True
170
+ else:
171
+ include_numbers = options.include_numbers
172
+
173
+ # Format list of choices
174
+ connector = ""
175
+ txt_builder: list[str] = []
176
+ if text:
177
+ txt_builder.append(text)
178
+ txt_builder.append("\n\n ")
179
+
180
+ for index, choice in enumerate(choices):
181
+ title = (
182
+ choice.action.title
183
+ if choice.action is not None and choice.action.title is not None
184
+ else choice.value
185
+ )
186
+
187
+ txt_builder.append(connector)
188
+ if include_numbers:
189
+ txt_builder.append(f"{index + 1}")
190
+ txt_builder.append(". ")
191
+ else:
192
+ txt_builder.append("- ")
193
+
194
+ txt_builder.append(title)
195
+ connector = "\n "
196
+
197
+ # Return activity with choices as a numbered list.
198
+ txt = "".join(txt_builder)
199
+ return MessageFactory.text(txt, speak, InputHints.expecting_input)
200
+
201
+ @staticmethod
202
+ def suggested_action(
203
+ choices: Iterable[Choice], text: str | None = None, speak: str | None = None
204
+ ) -> Activity:
205
+ """
206
+ Creates a message activity that includes a list of choices that have been added as suggested actions.
207
+ """
208
+ # Return activity with choices as suggested actions
209
+ return MessageFactory.suggested_actions(
210
+ ChoiceFactory._extract_actions(choices),
211
+ text,
212
+ speak,
213
+ InputHints.expecting_input,
214
+ )
215
+
216
+ @staticmethod
217
+ def hero_card(
218
+ choices: Iterable[Choice | str],
219
+ text: str | None = None,
220
+ speak: str | None = None,
221
+ ) -> Activity:
222
+ """
223
+ Creates a message activity that includes a lsit of coices that have been added as `HeroCard`'s
224
+ """
225
+ attachment = CardFactory.hero_card(
226
+ HeroCard(text=text or "", buttons=ChoiceFactory._extract_actions(choices))
227
+ )
228
+
229
+ # Return activity with choices as HeroCard with buttons
230
+ return MessageFactory.attachment(
231
+ attachment, None, speak, InputHints.expecting_input
232
+ )
233
+
234
+ @staticmethod
235
+ def _to_choices(choices: Iterable[str | Choice]) -> list[Choice]:
236
+ """
237
+ Takes a list of strings and returns them as [`Choice`].
238
+ """
239
+ if choices is None:
240
+ return []
241
+ return [
242
+ Choice(value=choice) if isinstance(choice, str) else choice
243
+ for choice in choices
244
+ ]
245
+
246
+ @staticmethod
247
+ def _extract_actions(choices: Iterable[str | Choice]) -> list[CardAction]:
248
+ if choices is None:
249
+ choices = []
250
+ choices = ChoiceFactory._to_choices(choices)
251
+ card_actions: list[CardAction] = []
252
+ for choice in choices:
253
+ if choice.action is not None:
254
+ card_action = choice.action
255
+ else:
256
+ card_action = CardAction(
257
+ type=ActionTypes.im_back, value=choice.value, title=choice.value
258
+ )
259
+
260
+ card_actions.append(card_action)
261
+
262
+ return card_actions