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,563 @@
1
+ # Copyright (c) Microsoft Corporation. All rights reserved.
2
+ # Licensed under the MIT License.
3
+
4
+ from __future__ import annotations
5
+
6
+ from typing import TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
9
+ from ..dialog_context import DialogContext
10
+
11
+ import builtins
12
+
13
+ from collections.abc import Callable, Iterable, Iterator
14
+ from inspect import isawaitable
15
+ from traceback import print_tb
16
+ from typing import TypeVar, cast
17
+
18
+ from .scopes.memory_scope import MemoryScope
19
+ from .component_memory_scopes_base import ComponentMemoryScopesBase
20
+ from .component_path_resolvers_base import ComponentPathResolversBase
21
+ from .dialog_path import DialogPath
22
+ from .dialog_state_manager_configuration import DialogStateManagerConfiguration
23
+
24
+ # Declare type variable
25
+ T = TypeVar("T") # pylint: disable=invalid-name
26
+
27
+ BUILTIN_TYPES = list(filter(lambda x: not x.startswith("_"), dir(builtins)))
28
+
29
+
30
+ # <summary>
31
+ # The DialogStateManager manages memory scopes and pathresolvers
32
+ # MemoryScopes are named root level objects, which can exist either in the dialogcontext or off of turn state
33
+ # PathResolvers allow for shortcut behavior for mapping things like $foo -> dialog.foo.
34
+ # </summary>
35
+ class DialogStateManager:
36
+ SEPARATORS = [",", "["]
37
+
38
+ def __init__(
39
+ self,
40
+ dialog_context: "DialogContext",
41
+ configuration: DialogStateManagerConfiguration | None = None,
42
+ ):
43
+ """
44
+ Initializes a new instance of the DialogStateManager class.
45
+ :param dialog_context: The dialog context for the current turn of the conversation.
46
+ :param configuration: Configuration for the dialog state manager. Default is None.
47
+ """
48
+ # pylint: disable=import-outside-toplevel
49
+ # These modules are imported at static level to avoid circular dependency problems
50
+ from microsoft_agents.hosting.dialogs import (
51
+ DialogsComponentRegistration,
52
+ ObjectPath,
53
+ )
54
+ from microsoft_agents.hosting.dialogs._component_registration import (
55
+ ComponentRegistration,
56
+ )
57
+
58
+ self._object_path_cls = ObjectPath
59
+ self._dialog_component_registration_cls = DialogsComponentRegistration
60
+
61
+ # Information for tracking when path was last modified.
62
+ self.path_tracker = "dialog._tracker.paths"
63
+
64
+ self._dialog_context = dialog_context
65
+ self._version: int = 0
66
+
67
+ if not dialog_context:
68
+ raise TypeError(f"Expecting: DialogContext, but received None")
69
+
70
+ from typing import cast as _cast # pylint: disable=import-outside-toplevel
71
+
72
+ self._configuration: DialogStateManagerConfiguration | None = (
73
+ configuration
74
+ or _cast(
75
+ DialogStateManagerConfiguration | None,
76
+ dialog_context.context.turn_state.get(
77
+ DialogStateManagerConfiguration.__name__, None
78
+ ),
79
+ )
80
+ )
81
+ if not self._configuration:
82
+ self._configuration = DialogStateManagerConfiguration()
83
+
84
+ ComponentRegistration.add(self._dialog_component_registration_cls())
85
+
86
+ # get all of the component memory scopes
87
+ memory_component: ComponentMemoryScopesBase
88
+ for memory_component in filter(
89
+ lambda comp: isinstance(comp, ComponentMemoryScopesBase),
90
+ ComponentRegistration.get_components(),
91
+ ):
92
+ for memory_scope in memory_component.get_memory_scopes():
93
+ self._configuration.memory_scopes.append(memory_scope)
94
+
95
+ # get all of the component path resolvers
96
+ path_component: ComponentPathResolversBase
97
+ for path_component in filter(
98
+ lambda comp: isinstance(comp, ComponentPathResolversBase),
99
+ ComponentRegistration.get_components(),
100
+ ):
101
+ for path_resolver in path_component.get_path_resolvers():
102
+ self._configuration.path_resolvers.append(path_resolver)
103
+
104
+ # cache for any other new dialog_state_manager instances in this turn.
105
+ dialog_context.context.turn_state[self._configuration.__class__.__name__] = (
106
+ self._configuration
107
+ )
108
+
109
+ def __len__(self) -> int:
110
+ """
111
+ Gets the number of memory scopes in the dialog state manager.
112
+ """
113
+ return len(self.configuration.memory_scopes)
114
+
115
+ @property
116
+ def configuration(self) -> DialogStateManagerConfiguration:
117
+ """
118
+ Gets or sets the configured path resolvers and memory scopes for the dialog state manager.
119
+ """
120
+ assert self._configuration is not None
121
+ return self._configuration
122
+
123
+ @property
124
+ def keys(self) -> Iterable[str]:
125
+ """
126
+ Gets a Iterable containing the keys of the memory scopes
127
+ """
128
+ return [memory_scope.name for memory_scope in self.configuration.memory_scopes]
129
+
130
+ @property
131
+ def values(self) -> Iterable[object]:
132
+ """
133
+ Gets a Iterable containing the values of the memory scopes.
134
+ """
135
+ return [
136
+ memory_scope.get_memory(self._dialog_context)
137
+ for memory_scope in self.configuration.memory_scopes
138
+ ]
139
+
140
+ @property
141
+ def is_read_only(self) -> bool:
142
+ """
143
+ Gets a value indicating whether the dialog state manager is read-only.
144
+ """
145
+ return True
146
+
147
+ def __getitem__(self, key):
148
+ """
149
+ :param key:
150
+ :return The value stored at key's position:
151
+ """
152
+ return self.get_value(object, key, default_value=lambda: None)
153
+
154
+ def __setitem__(self, key, value):
155
+ if self._index_of_any(key, self.SEPARATORS) == -1:
156
+ # Root is handled by SetMemory rather than SetValue
157
+ scope = self.get_memory_scope(key)
158
+ if not scope:
159
+ raise IndexError(self._get_bad_scope_message(key))
160
+ scope.set_memory(self._dialog_context, value)
161
+ else:
162
+ self.set_value(key, value)
163
+
164
+ def _get_bad_scope_message(self, path: str) -> str:
165
+ return (
166
+ f"'{path}' does not match memory scopes:["
167
+ f"{', '.join((memory_scope.name for memory_scope in self.configuration.memory_scopes))}]"
168
+ )
169
+
170
+ @staticmethod
171
+ def _index_of_any(string: str, elements_to_search_for) -> int:
172
+ for element in elements_to_search_for:
173
+ index = string.find(element)
174
+ if index != -1:
175
+ return index
176
+
177
+ return -1
178
+
179
+ def get_memory_scope(self, name: str) -> MemoryScope:
180
+ """
181
+ Get MemoryScope by name.
182
+ """
183
+ if not name:
184
+ raise TypeError(f"Expecting: {str.__name__}, but received None")
185
+
186
+ memory_scope = next(
187
+ (
188
+ memory_scope
189
+ for memory_scope in self.configuration.memory_scopes
190
+ if memory_scope.name.lower() == name.lower()
191
+ ),
192
+ None,
193
+ )
194
+
195
+ if not memory_scope:
196
+ raise IndexError(self._get_bad_scope_message(name))
197
+
198
+ return memory_scope
199
+
200
+ def version(self) -> str:
201
+ """
202
+ Version help caller to identify the updates and decide cache or not.
203
+ """
204
+ return str(self._version)
205
+
206
+ def resolve_memory_scope(self, path: str) -> tuple[MemoryScope, str]:
207
+ """
208
+ Will find the MemoryScope for and return the remaining path.
209
+ """
210
+ scope = path
211
+ sep_index = -1
212
+ dot = path.find(".")
213
+ open_square_bracket = path.find("[")
214
+
215
+ if dot > 0 and open_square_bracket > 0:
216
+ sep_index = min(dot, open_square_bracket)
217
+
218
+ elif dot > 0:
219
+ sep_index = dot
220
+
221
+ elif open_square_bracket > 0:
222
+ sep_index = open_square_bracket
223
+
224
+ if sep_index > 0:
225
+ scope = path[0:sep_index]
226
+ memory_scope = self.get_memory_scope(scope)
227
+ if memory_scope:
228
+ remaining_path = path[sep_index + 1 :]
229
+ return memory_scope, remaining_path
230
+
231
+ memory_scope = self.get_memory_scope(scope)
232
+ if not scope:
233
+ raise IndexError(self._get_bad_scope_message(scope))
234
+ return memory_scope, ""
235
+
236
+ def transform_path(self, path: str) -> str:
237
+ """
238
+ Transform the path using the registered PathTransformers.
239
+ """
240
+ for path_resolver in self.configuration.path_resolvers:
241
+ path = path_resolver.transform_path(path)
242
+
243
+ return path
244
+
245
+ @staticmethod
246
+ def _is_primitive(type_to_check: type) -> bool:
247
+ return type_to_check.__name__ in BUILTIN_TYPES
248
+
249
+ def try_get_value(
250
+ self, path: str, class_type: type = object
251
+ ) -> tuple[bool, object]:
252
+ """
253
+ Get the value from memory using path expression (NOTE: This always returns clone of value).
254
+ """
255
+ if not path:
256
+ raise TypeError(f"Expecting: {str.__name__}, but received None")
257
+ return_value = (
258
+ class_type() if DialogStateManager._is_primitive(class_type) else None
259
+ )
260
+ path = self.transform_path(path)
261
+
262
+ try:
263
+ memory_scope, remaining_path = self.resolve_memory_scope(path)
264
+ except Exception as error:
265
+ print_tb(error.__traceback__)
266
+ return False, return_value
267
+
268
+ if not memory_scope:
269
+ return False, return_value
270
+
271
+ if not remaining_path:
272
+ memory = memory_scope.get_memory(self._dialog_context)
273
+ if not memory:
274
+ return False, return_value
275
+
276
+ return True, memory
277
+
278
+ # TODO: HACK to support .First() retrieval on turn.recognized.entities.foo, replace with Expressions once
279
+ # expressions ship
280
+ first = ".FIRST()"
281
+ try:
282
+ i_first = path.upper().rindex(first)
283
+ except ValueError:
284
+ i_first = -1
285
+ if i_first >= 0:
286
+ remaining_path = path[i_first + len(first) :]
287
+ path = path[0:i_first]
288
+ success, first_value = self._try_get_first_nested_value(path, self)
289
+ if success:
290
+ if not remaining_path:
291
+ return True, first_value
292
+
293
+ path_value = self._object_path_cls.try_get_path_value(
294
+ first_value, remaining_path
295
+ )
296
+ return bool(path_value), path_value
297
+
298
+ return False, return_value
299
+
300
+ path_value = self._object_path_cls.try_get_path_value(self, path)
301
+ return bool(path_value), path_value
302
+
303
+ def get_value(
304
+ self,
305
+ class_type: type,
306
+ path_expression: str,
307
+ default_value: Callable[[], T] | None = None,
308
+ ) -> T | None:
309
+ """
310
+ Get the value from memory using path expression (NOTE: This always returns clone of value).
311
+ """
312
+ if not path_expression:
313
+ raise TypeError(f"Expecting: {str.__name__}, but received None")
314
+
315
+ success, value = self.try_get_value(path_expression, class_type)
316
+ if success:
317
+ return cast(T, value)
318
+
319
+ return default_value() if default_value else None
320
+
321
+ def get_int_value(self, path_expression: str, default_value: int = 0) -> int:
322
+ """
323
+ Get an int value from memory using a path expression.
324
+ """
325
+ if not path_expression:
326
+ raise TypeError(f"Expecting: {str.__name__}, but received None")
327
+ success, value = self.try_get_value(path_expression, int)
328
+ if success:
329
+ return cast(int, value)
330
+
331
+ return default_value
332
+
333
+ def get_bool_value(self, path_expression: str, default_value: bool = False) -> bool:
334
+ """
335
+ Get a bool value from memory using a path expression.
336
+ """
337
+ if not path_expression:
338
+ raise TypeError(f"Expecting: {str.__name__}, but received None")
339
+ success, value = self.try_get_value(path_expression, bool)
340
+ if success:
341
+ return cast(bool, value)
342
+
343
+ return default_value
344
+
345
+ def get_string_value(self, path_expression: str, default_value: str = "") -> str:
346
+ """
347
+ Get a string value from memory using a path expression.
348
+ """
349
+ if not path_expression:
350
+ raise TypeError(f"Expecting: {str.__name__}, but received None")
351
+ success, value = self.try_get_value(path_expression, str)
352
+ if success:
353
+ return cast(str, value)
354
+
355
+ return default_value
356
+
357
+ def set_value(self, path: str, value: object):
358
+ """
359
+ Set memory to value.
360
+ """
361
+ if isawaitable(value):
362
+ raise Exception(f"{path} = You can't pass an awaitable to set_value")
363
+
364
+ if not path:
365
+ raise TypeError(f"Expecting: {str.__name__}, but received None")
366
+
367
+ path = self.transform_path(path)
368
+ if self._track_change(path, value):
369
+ self._object_path_cls.set_path_value(self, path, value)
370
+
371
+ # Every set will increase version
372
+ self._version += 1
373
+
374
+ def remove_value(self, path: str):
375
+ """
376
+ Remove memory at the given path.
377
+ """
378
+ if not path:
379
+ raise TypeError(f"Expecting: {str.__name__}, but received None")
380
+
381
+ path = self.transform_path(path)
382
+ if self._track_change(path, None):
383
+ self._object_path_cls.remove_path_value(self, path)
384
+
385
+ def get_memory_snapshot(self) -> dict[str, object]:
386
+ """
387
+ Gets all memoryscopes suitable for logging.
388
+ """
389
+ result = {}
390
+
391
+ for scope in [
392
+ ms for ms in self.configuration.memory_scopes if ms.include_in_snapshot
393
+ ]:
394
+ memory = scope.get_memory(self._dialog_context)
395
+ if memory:
396
+ result[scope.name] = memory
397
+
398
+ return result
399
+
400
+ async def load_all_scopes(self):
401
+ """
402
+ Load all of the scopes.
403
+ """
404
+ for scope in self.configuration.memory_scopes:
405
+ await scope.load(self._dialog_context)
406
+
407
+ async def save_all_changes(self):
408
+ """
409
+ Save all changes for all scopes.
410
+ """
411
+ for scope in self.configuration.memory_scopes:
412
+ await scope.save_changes(self._dialog_context)
413
+
414
+ async def delete_scopes_memory_async(self, name: str):
415
+ """
416
+ Delete the memory for a scope.
417
+ """
418
+ name = name.upper()
419
+ scope_list = [
420
+ ms for ms in self.configuration.memory_scopes if ms.name.upper() == name
421
+ ]
422
+ if len(scope_list) > 1:
423
+ raise RuntimeError(f"More than 1 scopes found with the name '{name}'")
424
+ scope = scope_list[0] if scope_list else None
425
+ if scope:
426
+ await scope.delete(self._dialog_context)
427
+
428
+ def add(self, key: str, value: object):
429
+ raise RuntimeError("Not supported")
430
+
431
+ def contains_key(self, key: str) -> bool:
432
+ scopes_with_key = [
433
+ ms
434
+ for ms in self.configuration.memory_scopes
435
+ if ms.name.upper() == key.upper()
436
+ ]
437
+ return bool(scopes_with_key)
438
+
439
+ def remove(self, key: str):
440
+ raise RuntimeError("Not supported")
441
+
442
+ def clear(self, key: str):
443
+ raise RuntimeError("Not supported")
444
+
445
+ def contains(self, item: tuple[str, object]) -> bool:
446
+ raise RuntimeError("Not supported")
447
+
448
+ def __contains__(self, item: tuple[str, object]) -> bool:
449
+ raise RuntimeError("Not supported")
450
+
451
+ def copy_to(self, array: list[tuple[str, object]], array_index: int):
452
+ for memory_scope in self.configuration.memory_scopes:
453
+ array[array_index] = (
454
+ memory_scope.name,
455
+ memory_scope.get_memory(self._dialog_context),
456
+ )
457
+ array_index += 1
458
+
459
+ def remove_item(self, item: tuple[str, object]) -> bool:
460
+ raise RuntimeError("Not supported")
461
+
462
+ def get_enumerator(self) -> Iterator[tuple[str, object]]:
463
+ for memory_scope in self.configuration.memory_scopes:
464
+ yield (memory_scope.name, memory_scope.get_memory(self._dialog_context))
465
+
466
+ def track_paths(self, paths: Iterable[str]) -> list[str]:
467
+ """
468
+ Track when specific paths are changed.
469
+ """
470
+ all_paths = []
471
+ for path in paths:
472
+ t_path = self.transform_path(path)
473
+
474
+ # Track any path that resolves to a constant path
475
+ segments = self._object_path_cls.try_resolve_path(self, t_path)
476
+ if segments:
477
+ n_path = "_".join(segments)
478
+ self.set_value(self.path_tracker + "." + n_path, 0)
479
+ all_paths.append(n_path)
480
+
481
+ return all_paths
482
+
483
+ def any_path_changed(self, counter: int, paths: Iterable[str]) -> bool:
484
+ """
485
+ Check to see if any path has changed since watermark.
486
+ """
487
+ found = False
488
+ if paths:
489
+ for path in paths:
490
+ if self.get_int_value(self.path_tracker + "." + path) > counter:
491
+ found = True
492
+ break
493
+
494
+ return found
495
+
496
+ def __iter__(self):
497
+ for memory_scope in self.configuration.memory_scopes:
498
+ yield (memory_scope.name, memory_scope.get_memory(self._dialog_context))
499
+
500
+ @staticmethod
501
+ def _try_get_first_nested_value(
502
+ remaining_path: str, memory: object
503
+ ) -> tuple[bool, object]:
504
+ # pylint: disable=import-outside-toplevel
505
+ from microsoft_agents.hosting.dialogs import ObjectPath
506
+
507
+ array = ObjectPath.try_get_path_value(memory, remaining_path)
508
+ if array and isinstance(array, list):
509
+ if isinstance(array[0], list):
510
+ first = array[0]
511
+ if first:
512
+ second = first[0]
513
+ return True, second
514
+
515
+ return False, None
516
+
517
+ return True, array[0]
518
+
519
+ return False, None
520
+
521
+ def _track_change(self, path: str, value: object) -> bool:
522
+ has_path = False
523
+ segments = self._object_path_cls.try_resolve_path(self, path)
524
+ if segments:
525
+ root = segments[1] if len(segments) > 1 else ""
526
+
527
+ # Skip _* as first scope, i.e. _adaptive, _tracker, ...
528
+ if not root.startswith("_"):
529
+ # Convert to a simple path with _ between segments
530
+ path_name = "_".join(segments)
531
+ tracked_path = f"{self.path_tracker}.{path_name}"
532
+ counter = None
533
+
534
+ def update():
535
+ nonlocal counter
536
+ last_changed = self.try_get_value(tracked_path, int)
537
+ if last_changed:
538
+ if counter is not None:
539
+ counter = self.get_value(int, DialogPath.EVENT_COUNTER)
540
+
541
+ self.set_value(tracked_path, counter)
542
+
543
+ update()
544
+ if not self._is_primitive(type(value)):
545
+ # For an object we need to see if any children path are being tracked
546
+ def check_children(property: str, instance: object):
547
+ nonlocal tracked_path
548
+ # Add new child segment
549
+ tracked_path += "_" + property.lower()
550
+ update()
551
+ if not self._is_primitive(type(instance)):
552
+ self._object_path_cls.for_each_property(
553
+ property, check_children
554
+ )
555
+
556
+ # Remove added child segment
557
+ tracked_path = tracked_path[: tracked_path.rfind("_")]
558
+
559
+ self._object_path_cls.for_each_property(value, check_children)
560
+
561
+ has_path = True
562
+
563
+ return has_path
@@ -0,0 +1,11 @@
1
+ from dataclasses import dataclass, field
2
+
3
+ from .scopes.memory_scope import MemoryScope
4
+ from .path_resolver_base import PathResolverBase
5
+
6
+
7
+ @dataclass
8
+ class DialogStateManagerConfiguration:
9
+
10
+ path_resolvers: list[PathResolverBase] = field(default_factory=list)
11
+ memory_scopes: list[MemoryScope] = field(default_factory=list)
@@ -0,0 +1,8 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+
4
+ class PathResolverBase(ABC):
5
+
6
+ @abstractmethod
7
+ def transform_path(self, path: str):
8
+ raise NotImplementedError()
@@ -0,0 +1,19 @@
1
+ # Copyright (c) Microsoft Corporation. All rights reserved.
2
+ # Licensed under the MIT License.
3
+
4
+
5
+ from .alias_path_resolver import AliasPathResolver
6
+ from .at_at_path_resolver import AtAtPathResolver
7
+ from .at_path_resolver import AtPathResolver
8
+ from .dollar_path_resolver import DollarPathResolver
9
+ from .hash_path_resolver import HashPathResolver
10
+ from .percent_path_resolver import PercentPathResolver
11
+
12
+ __all__ = [
13
+ "AliasPathResolver",
14
+ "AtAtPathResolver",
15
+ "AtPathResolver",
16
+ "DollarPathResolver",
17
+ "HashPathResolver",
18
+ "PercentPathResolver",
19
+ ]
@@ -0,0 +1,53 @@
1
+ # Copyright (c) Microsoft Corporation. All rights reserved.
2
+ # Licensed under the MIT License.
3
+
4
+ from ..path_resolver_base import PathResolverBase
5
+
6
+
7
+ class AliasPathResolver(PathResolverBase):
8
+ def __init__(self, alias: str, prefix: str, postfix: str = ""):
9
+ """
10
+ Initializes a new instance of the <see cref="AliasPathResolver"/> class.
11
+ <param name="alias">Alias name.</param>
12
+ <param name="prefix">Prefix name.</param>
13
+ <param name="postfix">Postfix name.</param>
14
+ """
15
+ if alias is None:
16
+ raise TypeError(f"Expecting: alias, but received None")
17
+ if prefix is None:
18
+ raise TypeError(f"Expecting: prefix, but received None")
19
+
20
+ # Gets the alias name.
21
+ self.alias = alias.strip()
22
+ self._prefix = prefix.strip()
23
+ self._postfix = postfix.strip()
24
+
25
+ def transform_path(self, path: str):
26
+ """
27
+ Transforms the path.
28
+ <param name="path">Path to inspect.</param>
29
+ <returns>Transformed path.</returns>
30
+ """
31
+ if not path:
32
+ raise TypeError(f"Expecting: path, but received None")
33
+
34
+ path = path.strip()
35
+ if (
36
+ path.startswith(self.alias)
37
+ and len(path) > len(self.alias)
38
+ and AliasPathResolver._is_path_char(path[len(self.alias)])
39
+ ):
40
+ # here we only deals with trailing alias, alias in middle be handled in further breakdown
41
+ # $xxx -> path.xxx
42
+ return f"{self._prefix}{path[len(self.alias):]}{self._postfix}".rstrip(".")
43
+
44
+ return path
45
+
46
+ @staticmethod
47
+ def _is_path_char(char: str) -> bool:
48
+ """
49
+ Verifies if a character is valid for a path.
50
+ <param name="ch">Character to verify.</param>
51
+ <returns><c>true</c> if the character is valid for a path otherwise, <c>false</c>.</returns>
52
+ """
53
+ return len(char) == 1 and (char.isalpha() or char == "_")
@@ -0,0 +1,9 @@
1
+ # Copyright (c) Microsoft Corporation. All rights reserved.
2
+ # Licensed under the MIT License.
3
+
4
+ from .alias_path_resolver import AliasPathResolver
5
+
6
+
7
+ class AtAtPathResolver(AliasPathResolver):
8
+ def __init__(self):
9
+ super().__init__(alias="@@", prefix="turn.recognized.entities.")