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,203 @@
1
+ # Copyright (c) Microsoft Corporation. All rights reserved.
2
+ # Licensed under the MIT License.
3
+
4
+ from typing import Any, Callable, cast
5
+
6
+ from microsoft_agents.hosting.core import TurnContext
7
+ from microsoft_agents.activity import ActivityTypes, InputHints
8
+
9
+ from ..dialog import Dialog
10
+ from ..dialog_context import DialogContext
11
+ from ..models.dialog_instance import DialogInstance
12
+ from ..models.dialog_reason import DialogReason
13
+ from ..models.dialog_turn_result import DialogTurnResult
14
+ from .prompt import Prompt
15
+ from .prompt_options import PromptOptions
16
+ from .prompt_recognizer_result import PromptRecognizerResult
17
+ from .prompt_validator_context import PromptValidatorContext
18
+
19
+
20
+ class ActivityPrompt(Dialog):
21
+ """
22
+ Waits for an activity to be received.
23
+
24
+ This prompt requires a validator be passed in and is useful when waiting for non-message
25
+ activities like an event to be received. The validator can ignore received events until the
26
+ expected activity is received.
27
+ """
28
+
29
+ persisted_options = "options"
30
+ persisted_state = "state"
31
+
32
+ def __init__(
33
+ self, dialog_id: str, validator: Callable[[PromptValidatorContext], Any]
34
+ ):
35
+ Dialog.__init__(self, dialog_id)
36
+
37
+ if validator is None:
38
+ raise TypeError("validator was expected but received None")
39
+ self._validator = validator
40
+
41
+ async def begin_dialog(
42
+ self, dialog_context: DialogContext, options: object = None
43
+ ) -> DialogTurnResult:
44
+ """Starts the prompt by sending the initial prompt activity.
45
+
46
+ Initialises persisted state with an attempt count of 0, then calls
47
+ :meth:`on_prompt`.
48
+
49
+ :param dialog_context: The dialog context for the current turn.
50
+ :param options: Must be a :class:`PromptOptions` instance.
51
+ :raises TypeError: If ``options`` is not a :class:`PromptOptions`.
52
+ :return: :attr:`Dialog.end_of_turn` to wait for the user's response.
53
+ """
54
+ if not dialog_context:
55
+ raise TypeError("ActivityPrompt.begin_dialog(): dc cannot be None.")
56
+ if not isinstance(options, PromptOptions):
57
+ raise TypeError(
58
+ "ActivityPrompt.begin_dialog(): Prompt options are required for ActivityPrompts."
59
+ )
60
+
61
+ # Ensure prompts have input hint set
62
+ if options.prompt is not None and not options.prompt.input_hint:
63
+ options.prompt.input_hint = InputHints.expecting_input
64
+
65
+ if options.retry_prompt is not None and not options.retry_prompt.input_hint:
66
+ options.retry_prompt.input_hint = InputHints.expecting_input
67
+
68
+ # Initialize prompt state
69
+ assert dialog_context.active_dialog is not None
70
+ state: dict[str, object] = dialog_context.active_dialog.state
71
+ state[self.persisted_options] = options
72
+ state[self.persisted_state] = {Prompt.ATTEMPT_COUNT_KEY: 0}
73
+
74
+ # Send initial prompt
75
+ await self.on_prompt(
76
+ dialog_context.context,
77
+ cast(dict[str, object], state[self.persisted_state]),
78
+ cast(PromptOptions, state[self.persisted_options]),
79
+ False,
80
+ )
81
+
82
+ return Dialog.end_of_turn
83
+
84
+ async def continue_dialog(self, dialog_context: DialogContext) -> DialogTurnResult:
85
+ """Processes the next incoming activity through the prompt.
86
+
87
+ Increments the persisted attempt count **before** calling the validator,
88
+ so :attr:`PromptValidatorContext.attempt_count` is at least 1 on the
89
+ first validation call.
90
+
91
+ .. note::
92
+ This differs from the base :class:`Prompt` class, where
93
+ ``attempt_count`` is never stored in state and is always 0 when the
94
+ validator runs. Code that validates both ``ActivityPrompt`` and
95
+ ``Prompt`` subclasses should rely on
96
+ :attr:`PromptOptions.number_of_attempts` for consistent counting.
97
+
98
+ :param dialog_context: The dialog context for the current turn.
99
+ :return: :attr:`Dialog.end_of_turn` while waiting for valid input, or
100
+ a Complete result once the validator accepts the activity.
101
+ """
102
+ if not dialog_context:
103
+ raise TypeError(
104
+ "ActivityPrompt.continue_dialog(): DialogContext cannot be None."
105
+ )
106
+
107
+ # Perform base recognition
108
+ instance = dialog_context.active_dialog
109
+ assert instance is not None
110
+ state: dict[str, object] = cast(
111
+ dict[str, object], instance.state[self.persisted_state]
112
+ )
113
+ prompt_options: PromptOptions = cast(
114
+ PromptOptions, instance.state[self.persisted_options]
115
+ )
116
+ recognized: PromptRecognizerResult = await self.on_recognize(
117
+ dialog_context.context, state, prompt_options
118
+ )
119
+
120
+ # Increment attempt count
121
+ state[Prompt.ATTEMPT_COUNT_KEY] = (
122
+ int(cast(int, state.get(Prompt.ATTEMPT_COUNT_KEY, 0))) + 1
123
+ )
124
+
125
+ # Validate the return value
126
+ is_valid = False
127
+ if self._validator is not None:
128
+ prompt_context = PromptValidatorContext(
129
+ dialog_context.context, recognized, state, prompt_options
130
+ )
131
+ is_valid = await self._validator(prompt_context)
132
+
133
+ prompt_options.number_of_attempts += 1
134
+ elif recognized.succeeded:
135
+ is_valid = True
136
+
137
+ # Return recognized value or re-prompt
138
+ if is_valid:
139
+ return await dialog_context.end_dialog(recognized.value)
140
+
141
+ if (
142
+ dialog_context.context.activity.type == ActivityTypes.message
143
+ and not dialog_context.context.responded
144
+ ):
145
+ await self.on_prompt(dialog_context.context, state, prompt_options, True)
146
+
147
+ return Dialog.end_of_turn
148
+
149
+ async def resume_dialog( # pylint: disable=unused-argument
150
+ self, dialog_context: DialogContext, reason: DialogReason, result: object = None
151
+ ):
152
+ assert dialog_context.active_dialog is not None
153
+ await self.reprompt_dialog(dialog_context.context, dialog_context.active_dialog)
154
+
155
+ return Dialog.end_of_turn
156
+
157
+ async def reprompt_dialog(self, context: TurnContext, instance: DialogInstance):
158
+ state: dict[str, object] = instance.state[self.persisted_state]
159
+ options: PromptOptions = instance.state[self.persisted_options]
160
+ await self.on_prompt(context, state, options, False)
161
+
162
+ async def on_prompt(
163
+ self,
164
+ context: TurnContext,
165
+ state: dict[str, object], # pylint: disable=unused-argument
166
+ options: PromptOptions,
167
+ is_retry: bool = False,
168
+ ):
169
+ """Sends the initial or retry prompt activity to the user.
170
+
171
+ Always sets ``input_hint`` to ``expecting_input`` before sending.
172
+
173
+ :param context: The context for the current turn.
174
+ :param state: Persisted prompt state (unused by default implementation).
175
+ :param options: Prompt options containing the prompt and optional retry prompt.
176
+ :param is_retry: ``True`` when re-prompting after failed validation.
177
+ """
178
+ if is_retry and options.retry_prompt:
179
+ options.retry_prompt.input_hint = InputHints.expecting_input
180
+ await context.send_activity(options.retry_prompt)
181
+ elif options.prompt:
182
+ options.prompt.input_hint = InputHints.expecting_input
183
+ await context.send_activity(options.prompt)
184
+
185
+ async def on_recognize( # pylint: disable=unused-argument
186
+ self, context: TurnContext, state: dict[str, object], options: PromptOptions
187
+ ) -> PromptRecognizerResult:
188
+ """Default recognizer: always succeeds and returns the raw incoming activity.
189
+
190
+ Override this in a subclass to restrict which activities are considered
191
+ valid (e.g. accept only events with a specific name).
192
+
193
+ :param context: The context for the current turn.
194
+ :param state: Persisted prompt state.
195
+ :param options: Prompt options.
196
+ :return: A result with ``succeeded=True`` and ``value`` set to the
197
+ current :class:`Activity`.
198
+ """
199
+ result = PromptRecognizerResult()
200
+ result.succeeded = True
201
+ result.value = context.activity
202
+
203
+ return result
@@ -0,0 +1,87 @@
1
+ # Copyright (c) Microsoft Corporation. All rights reserved.
2
+ # Licensed under the MIT License.
3
+
4
+ from typing import Callable
5
+
6
+ from microsoft_agents.hosting.core import TurnContext
7
+ from microsoft_agents.activity import ActivityTypes
8
+
9
+ from .prompt import Prompt, PromptValidatorContext
10
+ from .prompt_options import PromptOptions
11
+ from .prompt_recognizer_result import PromptRecognizerResult
12
+
13
+
14
+ class AttachmentPrompt(Prompt):
15
+ """
16
+ Prompts a user to upload attachments like images.
17
+
18
+ By default the prompt will return to the calling dialog an `[Attachment]`
19
+ """
20
+
21
+ def __init__(
22
+ self,
23
+ dialog_id: str,
24
+ validator: Callable[[PromptValidatorContext], bool] | None = None,
25
+ ):
26
+ super().__init__(dialog_id, validator)
27
+
28
+ async def on_prompt(
29
+ self,
30
+ turn_context: TurnContext,
31
+ state: dict[str, object],
32
+ options: PromptOptions,
33
+ is_retry: bool,
34
+ ):
35
+ """Sends the initial or retry prompt activity to the user.
36
+
37
+ :param turn_context: The context for the current turn.
38
+ :param state: Persisted prompt state (unused by AttachmentPrompt).
39
+ :param options: Prompt options containing the prompt and optional retry prompt.
40
+ :param is_retry: ``True`` when re-prompting after failed validation.
41
+ """
42
+ if not turn_context:
43
+ raise TypeError("AttachmentPrompt.on_prompt(): TurnContext cannot be None.")
44
+
45
+ if not isinstance(options, PromptOptions):
46
+ raise TypeError(
47
+ "AttachmentPrompt.on_prompt(): PromptOptions are required for Attachment Prompt dialogs."
48
+ )
49
+
50
+ if is_retry and options.retry_prompt:
51
+ await turn_context.send_activity(options.retry_prompt)
52
+ elif options.prompt:
53
+ await turn_context.send_activity(options.prompt)
54
+
55
+ async def on_recognize(
56
+ self,
57
+ turn_context: TurnContext,
58
+ state: dict[str, object],
59
+ options: PromptOptions,
60
+ ) -> PromptRecognizerResult:
61
+ """Attempts to recognise attachments from the incoming activity.
62
+
63
+ Succeeds only when the activity is a message **and** ``activity.attachments``
64
+ is a non-empty list. The full attachment list is returned as the result.
65
+
66
+ .. note::
67
+ A message activity with no attachments (e.g. plain text) will fail
68
+ recognition and trigger the retry prompt.
69
+
70
+ :param turn_context: The context for the current turn.
71
+ :param state: Persisted prompt state (unused by AttachmentPrompt).
72
+ :param options: Prompt options (unused by AttachmentPrompt).
73
+ :return: Recognition result with ``succeeded=True`` and the list of
74
+ :class:`Attachment` objects, or ``succeeded=False`` if none were found.
75
+ """
76
+ if not turn_context:
77
+ raise TypeError("AttachmentPrompt.on_recognize(): context cannot be None.")
78
+
79
+ result = PromptRecognizerResult()
80
+
81
+ if turn_context.activity.type == ActivityTypes.message:
82
+ message = turn_context.activity
83
+ if isinstance(message.attachments, list) and message.attachments:
84
+ result.succeeded = True
85
+ result.value = message.attachments
86
+
87
+ return result
@@ -0,0 +1,156 @@
1
+ # Copyright (c) Microsoft Corporation. All rights reserved.
2
+ # Licensed under the MIT License.
3
+
4
+ from typing import Callable
5
+
6
+ from microsoft_agents.hosting.core import TurnContext
7
+ from microsoft_agents.activity import Activity, ActivityTypes
8
+
9
+ from ..choices import (
10
+ Choice,
11
+ ChoiceFactoryOptions,
12
+ ChoiceRecognizers,
13
+ FindChoicesOptions,
14
+ ListStyle,
15
+ )
16
+
17
+ from .prompt import Prompt
18
+ from .prompt_culture_models import PromptCultureModels
19
+ from .prompt_options import PromptOptions
20
+ from .prompt_validator_context import PromptValidatorContext
21
+ from .prompt_recognizer_result import PromptRecognizerResult
22
+
23
+
24
+ class ChoicePrompt(Prompt):
25
+ """
26
+ Prompts a user to select from a list of choices.
27
+
28
+ By default the prompt will return to the calling dialog a `FoundChoice` object containing the choice that
29
+ was selected.
30
+ """
31
+
32
+ _default_choice_options: dict[str, ChoiceFactoryOptions] = {
33
+ c.locale: ChoiceFactoryOptions(
34
+ inline_separator=c.separator,
35
+ inline_or=c.inline_or,
36
+ inline_or_more=c.inline_or_more,
37
+ include_numbers=True,
38
+ )
39
+ for c in PromptCultureModels.get_supported_cultures()
40
+ }
41
+
42
+ def __init__(
43
+ self,
44
+ dialog_id: str,
45
+ validator: Callable[[PromptValidatorContext], bool] | None = None,
46
+ default_locale: str | None = None,
47
+ choice_defaults: dict[str, ChoiceFactoryOptions] | None = None,
48
+ ):
49
+ super().__init__(dialog_id, validator)
50
+
51
+ self.style = ListStyle.auto
52
+ self.default_locale = default_locale
53
+ self.choice_options: ChoiceFactoryOptions | None = None
54
+ self.recognizer_options: FindChoicesOptions | None = None
55
+
56
+ if choice_defaults is not None:
57
+ self._default_choice_options = choice_defaults
58
+
59
+ async def on_prompt(
60
+ self,
61
+ turn_context: TurnContext,
62
+ state: dict[str, object],
63
+ options: PromptOptions,
64
+ is_retry: bool,
65
+ ):
66
+ if not turn_context:
67
+ raise TypeError("ChoicePrompt.on_prompt(): turn_context cannot be None.")
68
+
69
+ if not options:
70
+ raise TypeError("ChoicePrompt.on_prompt(): options cannot be None.")
71
+
72
+ # Determine culture
73
+ culture = self._determine_culture(turn_context.activity)
74
+
75
+ # Format prompt to send
76
+ choices: list[Choice] = options.choices if options.choices else []
77
+ channel_id: str = turn_context.activity.channel_id or ""
78
+ choice_options: ChoiceFactoryOptions = (
79
+ self.choice_options
80
+ if self.choice_options
81
+ else self._default_choice_options[culture]
82
+ )
83
+ choice_style = (
84
+ 0 if options.style == 0 else options.style if options.style else self.style
85
+ )
86
+
87
+ if is_retry and options.retry_prompt is not None:
88
+ prompt = self.append_choices(
89
+ options.retry_prompt, channel_id, choices, choice_style, choice_options
90
+ )
91
+ else:
92
+ prompt = self.append_choices(
93
+ options.prompt, channel_id, choices, choice_style, choice_options
94
+ )
95
+
96
+ # Send prompt
97
+ await turn_context.send_activity(prompt)
98
+
99
+ async def on_recognize(
100
+ self,
101
+ turn_context: TurnContext,
102
+ state: dict[str, object],
103
+ options: PromptOptions,
104
+ ) -> PromptRecognizerResult:
105
+ if not turn_context:
106
+ raise TypeError("ChoicePrompt.on_recognize(): turn_context cannot be None.")
107
+
108
+ choices: list[Choice] = options.choices if (options and options.choices) else []
109
+ result: PromptRecognizerResult = PromptRecognizerResult()
110
+
111
+ if turn_context.activity.type == ActivityTypes.message:
112
+ activity: Activity = turn_context.activity
113
+ utterance: str = activity.text
114
+ if not utterance:
115
+ return result
116
+ opt: FindChoicesOptions = (
117
+ self.recognizer_options
118
+ if self.recognizer_options
119
+ else FindChoicesOptions()
120
+ )
121
+ opt.locale = self._determine_culture(turn_context.activity, opt)
122
+ results = ChoiceRecognizers.recognize_choices(utterance, choices, opt)
123
+
124
+ if results is not None and results:
125
+ result.succeeded = True
126
+ result.value = results[0].resolution
127
+
128
+ return result
129
+
130
+ def _determine_culture(
131
+ self, activity: Activity, opt: FindChoicesOptions | None = None
132
+ ) -> str:
133
+ """Resolves the culture/locale string to use for choice formatting and recognition.
134
+
135
+ Resolution order:
136
+ 1. ``activity.locale`` (mapped to the nearest supported language)
137
+ 2. ``self.default_locale`` set at construction
138
+ 3. ``opt.locale`` from the recognizer options (if provided)
139
+ 4. English (``en-us``) as the final fallback
140
+
141
+ If the resolved locale is not in ``_default_choice_options``, English is used.
142
+
143
+ :param activity: The incoming activity (provides ``locale``).
144
+ :param opt: Optional recogniser options (provides ``locale`` fallback).
145
+ :return: A locale string present in :attr:`_default_choice_options`.
146
+ """
147
+ culture = (
148
+ PromptCultureModels.map_to_nearest_language(activity.locale)
149
+ or self.default_locale
150
+ or (opt.locale if opt else None)
151
+ or PromptCultureModels.English.locale
152
+ )
153
+ if not culture or not self._default_choice_options.get(culture):
154
+ culture = PromptCultureModels.English.locale
155
+
156
+ return culture
@@ -0,0 +1,161 @@
1
+ # Copyright (c) Microsoft Corporation. All rights reserved.
2
+ # Licensed under the MIT License.
3
+
4
+ from typing import Any, Callable
5
+
6
+ from recognizers_choice import recognize_boolean
7
+
8
+ from microsoft_agents.hosting.core import TurnContext
9
+ from microsoft_agents.activity import ActivityTypes, Activity
10
+
11
+ from ..choices import (
12
+ Choice,
13
+ ChoiceFactoryOptions,
14
+ ChoiceRecognizers,
15
+ ListStyle,
16
+ )
17
+
18
+ from .prompt import Prompt
19
+ from .prompt_culture_models import PromptCultureModels
20
+ from .prompt_options import PromptOptions
21
+ from .prompt_recognizer_result import PromptRecognizerResult
22
+ from .prompt_validator_context import PromptValidatorContext
23
+
24
+
25
+ class ConfirmPrompt(Prompt):
26
+ """Prompts a user to confirm something with a yes/no response.
27
+
28
+ The prompt first attempts to recognise a boolean value using the
29
+ ``recognizers_choice`` package (e.g. "yes", "no", "true", "false", locale
30
+ equivalents). If that fails and the prompt was configured to include
31
+ numbered choices (the default), it falls back to matching choice indices
32
+ ("1" → yes, "2" → no).
33
+
34
+ Returns ``True`` for confirmation, ``False`` for denial.
35
+
36
+ Culture/locale detection uses :class:`PromptCultureModels`. Override
37
+ ``default_locale`` or ``choice_defaults`` at construction time to customise
38
+ the displayed choices and the locale used for recognition.
39
+ """
40
+
41
+ _default_choice_options: dict[str, tuple[Choice, Choice, ChoiceFactoryOptions]] = {
42
+ c.locale: (
43
+ Choice(c.yes_in_language),
44
+ Choice(c.no_in_language),
45
+ ChoiceFactoryOptions(c.separator, c.inline_or, c.inline_or_more, True),
46
+ )
47
+ for c in PromptCultureModels.get_supported_cultures()
48
+ }
49
+
50
+ def __init__(
51
+ self,
52
+ dialog_id: str,
53
+ validator: Callable[[PromptValidatorContext], Any] | None = None,
54
+ default_locale: str | None = None,
55
+ choice_defaults: (
56
+ dict[str, tuple[Choice, Choice, ChoiceFactoryOptions]] | None
57
+ ) = None,
58
+ ):
59
+ super().__init__(dialog_id, validator)
60
+ if dialog_id is None:
61
+ raise TypeError("ConfirmPrompt(): dialog_id cannot be None.")
62
+ self.style = ListStyle.auto
63
+ self.default_locale = default_locale
64
+ self.choice_options = None
65
+ self.confirm_choices = None
66
+
67
+ if choice_defaults is not None:
68
+ self._default_choice_options = choice_defaults
69
+
70
+ async def on_prompt(
71
+ self,
72
+ turn_context: TurnContext,
73
+ state: dict[str, object],
74
+ options: PromptOptions,
75
+ is_retry: bool,
76
+ ):
77
+ if not turn_context:
78
+ raise TypeError("ConfirmPrompt.on_prompt(): turn_context cannot be None.")
79
+ if not options:
80
+ raise TypeError("ConfirmPrompt.on_prompt(): options cannot be None.")
81
+
82
+ # Format prompt to send
83
+ channel_id = turn_context.activity.channel_id or ""
84
+ culture = self._determine_culture(turn_context.activity)
85
+ defaults = self._default_choice_options[culture]
86
+ choice_opts = (
87
+ self.choice_options if self.choice_options is not None else defaults[2]
88
+ )
89
+ confirms = (
90
+ self.confirm_choices
91
+ if self.confirm_choices is not None
92
+ else (defaults[0], defaults[1])
93
+ )
94
+ choices = [confirms[0], confirms[1]]
95
+ if is_retry and options.retry_prompt is not None:
96
+ prompt = self.append_choices(
97
+ options.retry_prompt, channel_id, choices, self.style, choice_opts
98
+ )
99
+ else:
100
+ prompt = self.append_choices(
101
+ options.prompt, channel_id, choices, self.style, choice_opts
102
+ )
103
+ await turn_context.send_activity(prompt)
104
+
105
+ async def on_recognize(
106
+ self,
107
+ turn_context: TurnContext,
108
+ state: dict[str, object],
109
+ options: PromptOptions,
110
+ ) -> PromptRecognizerResult:
111
+ if not turn_context:
112
+ raise TypeError("ConfirmPrompt.on_prompt(): turn_context cannot be None.")
113
+
114
+ result = PromptRecognizerResult()
115
+ if turn_context.activity.type == ActivityTypes.message:
116
+ # Recognize utterance
117
+ utterance = turn_context.activity.text
118
+ if not utterance:
119
+ return result
120
+ culture = self._determine_culture(turn_context.activity)
121
+ results = recognize_boolean(utterance, culture)
122
+ if results:
123
+ first = results[0]
124
+ if "value" in first.resolution:
125
+ result.succeeded = True
126
+ result.value = first.resolution["value"]
127
+ else:
128
+ # First check whether the prompt was sent to the user with numbers
129
+ defaults = self._default_choice_options[culture]
130
+ opts = (
131
+ self.choice_options
132
+ if self.choice_options is not None
133
+ else defaults[2]
134
+ )
135
+
136
+ if opts.include_numbers is None or opts.include_numbers:
137
+ confirm_choices = (
138
+ self.confirm_choices
139
+ if self.confirm_choices is not None
140
+ else (defaults[0], defaults[1])
141
+ )
142
+ choices = [confirm_choices[0], confirm_choices[1]]
143
+ second_attempt_results = ChoiceRecognizers.recognize_choices(
144
+ utterance, choices
145
+ )
146
+ if second_attempt_results:
147
+ result.succeeded = True
148
+ result.value = second_attempt_results[0].resolution.index == 0
149
+
150
+ return result
151
+
152
+ def _determine_culture(self, activity: Activity) -> str:
153
+ culture = (
154
+ PromptCultureModels.map_to_nearest_language(activity.locale)
155
+ or self.default_locale
156
+ or PromptCultureModels.English.locale
157
+ )
158
+ if not culture or not self._default_choice_options.get(culture):
159
+ culture = PromptCultureModels.English.locale
160
+
161
+ return culture
@@ -0,0 +1,90 @@
1
+ # Copyright (c) Microsoft Corporation. All rights reserved.
2
+ # Licensed under the MIT License.
3
+
4
+ from typing import Any, Callable, cast
5
+
6
+ from recognizers_date_time import recognize_datetime
7
+ from recognizers_text import Culture
8
+
9
+ from microsoft_agents.hosting.core import TurnContext
10
+ from microsoft_agents.activity import ActivityTypes
11
+
12
+ from .datetime_resolution import DateTimeResolution
13
+ from .prompt import Prompt
14
+ from .prompt_options import PromptOptions
15
+ from .prompt_recognizer_result import PromptRecognizerResult
16
+ from .prompt_validator_context import PromptValidatorContext
17
+
18
+
19
+ class DateTimePrompt(Prompt):
20
+ def __init__(
21
+ self,
22
+ dialog_id: str,
23
+ validator: Callable[[PromptValidatorContext], Any] | None = None,
24
+ default_locale: str | None = None,
25
+ ):
26
+ super(DateTimePrompt, self).__init__(dialog_id, validator)
27
+ self.default_locale = default_locale
28
+
29
+ async def on_prompt(
30
+ self,
31
+ turn_context: TurnContext,
32
+ state: dict[str, object],
33
+ options: PromptOptions,
34
+ is_retry: bool,
35
+ ):
36
+ if not turn_context:
37
+ raise TypeError("DateTimePrompt.on_prompt(): turn_context cannot be None.")
38
+ if not options:
39
+ raise TypeError("DateTimePrompt.on_prompt(): options cannot be None.")
40
+
41
+ if is_retry and options.retry_prompt is not None:
42
+ await turn_context.send_activity(options.retry_prompt)
43
+ else:
44
+ if options.prompt is not None:
45
+ await turn_context.send_activity(options.prompt)
46
+
47
+ async def on_recognize(
48
+ self,
49
+ turn_context: TurnContext,
50
+ state: dict[str, object],
51
+ options: PromptOptions,
52
+ ) -> PromptRecognizerResult:
53
+ if not turn_context:
54
+ raise TypeError(
55
+ "DateTimePrompt.on_recognize(): turn_context cannot be None."
56
+ )
57
+
58
+ result = PromptRecognizerResult()
59
+ if turn_context.activity.type == ActivityTypes.message:
60
+ # Recognize utterance
61
+ utterance = turn_context.activity.text
62
+ if not utterance:
63
+ return result
64
+ culture = (
65
+ turn_context.activity.locale or self.default_locale or Culture.English
66
+ )
67
+
68
+ results = recognize_datetime(utterance, culture)
69
+ if results:
70
+ result.succeeded = True
71
+ result.value = []
72
+ values = cast(list, results[0].resolution["values"])
73
+ for value in values:
74
+ cast(list, result.value).append(self.read_resolution(value))
75
+
76
+ return result
77
+
78
+ def read_resolution(self, resolution: dict[str, str]) -> DateTimeResolution:
79
+ result = DateTimeResolution()
80
+
81
+ if "timex" in resolution:
82
+ result.timex = resolution["timex"]
83
+ if "value" in resolution:
84
+ result.value = resolution["value"]
85
+ if "start" in resolution:
86
+ result.start = resolution["start"]
87
+ if "end" in resolution:
88
+ result.end = resolution["end"]
89
+
90
+ return result