waldiez 0.6.0__py3-none-any.whl → 0.6.1__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.

Potentially problematic release.


This version of waldiez might be problematic. Click here for more details.

Files changed (188) hide show
  1. waldiez/__init__.py +1 -1
  2. waldiez/_version.py +1 -1
  3. waldiez/cli.py +18 -7
  4. waldiez/cli_extras/jupyter.py +3 -0
  5. waldiez/cli_extras/runner.py +3 -1
  6. waldiez/cli_extras/studio.py +3 -1
  7. waldiez/exporter.py +9 -3
  8. waldiez/exporting/agent/exporter.py +9 -10
  9. waldiez/exporting/agent/extras/captain_agent_extras.py +6 -6
  10. waldiez/exporting/agent/extras/doc_agent_extras.py +6 -6
  11. waldiez/exporting/agent/extras/group_manager_agent_extas.py +34 -23
  12. waldiez/exporting/agent/extras/group_member_extras.py +6 -5
  13. waldiez/exporting/agent/extras/handoffs/after_work.py +1 -1
  14. waldiez/exporting/agent/extras/handoffs/available.py +1 -1
  15. waldiez/exporting/agent/extras/handoffs/condition.py +3 -2
  16. waldiez/exporting/agent/extras/handoffs/handoff.py +1 -1
  17. waldiez/exporting/agent/extras/handoffs/target.py +6 -4
  18. waldiez/exporting/agent/extras/rag/chroma_extras.py +27 -19
  19. waldiez/exporting/agent/extras/rag/mongo_extras.py +8 -8
  20. waldiez/exporting/agent/extras/rag/pgvector_extras.py +5 -5
  21. waldiez/exporting/agent/extras/rag/qdrant_extras.py +5 -4
  22. waldiez/exporting/agent/extras/rag/vector_db_extras.py +1 -1
  23. waldiez/exporting/agent/extras/rag_user_proxy_agent_extras.py +5 -7
  24. waldiez/exporting/agent/extras/reasoning_agent_extras.py +3 -5
  25. waldiez/exporting/chats/exporter.py +4 -4
  26. waldiez/exporting/chats/processor.py +1 -2
  27. waldiez/exporting/chats/utils/common.py +89 -48
  28. waldiez/exporting/chats/utils/group.py +9 -9
  29. waldiez/exporting/chats/utils/nested.py +7 -7
  30. waldiez/exporting/chats/utils/sequential.py +1 -1
  31. waldiez/exporting/chats/utils/single.py +2 -2
  32. waldiez/exporting/core/content.py +7 -7
  33. waldiez/exporting/core/context.py +5 -3
  34. waldiez/exporting/core/exporter.py +5 -3
  35. waldiez/exporting/core/exporters.py +2 -2
  36. waldiez/exporting/core/extras/agent_extras/captain_extras.py +2 -2
  37. waldiez/exporting/core/extras/agent_extras/group_manager_extras.py +2 -2
  38. waldiez/exporting/core/extras/agent_extras/rag_user_extras.py +2 -2
  39. waldiez/exporting/core/extras/agent_extras/standard_extras.py +3 -8
  40. waldiez/exporting/core/extras/base.py +7 -5
  41. waldiez/exporting/core/extras/flow_extras.py +4 -5
  42. waldiez/exporting/core/extras/model_extras.py +2 -2
  43. waldiez/exporting/core/extras/path_resolver.py +1 -2
  44. waldiez/exporting/core/extras/serializer.py +2 -2
  45. waldiez/exporting/core/protocols.py +6 -5
  46. waldiez/exporting/core/result.py +25 -28
  47. waldiez/exporting/core/types.py +10 -10
  48. waldiez/exporting/core/utils/llm_config.py +2 -2
  49. waldiez/exporting/core/validation.py +10 -11
  50. waldiez/exporting/flow/execution_generator.py +98 -10
  51. waldiez/exporting/flow/exporter.py +2 -2
  52. waldiez/exporting/flow/factory.py +2 -2
  53. waldiez/exporting/flow/file_generator.py +4 -2
  54. waldiez/exporting/flow/merger.py +5 -3
  55. waldiez/exporting/flow/orchestrator.py +72 -2
  56. waldiez/exporting/flow/utils/common.py +5 -5
  57. waldiez/exporting/flow/utils/importing.py +6 -7
  58. waldiez/exporting/flow/utils/linting.py +25 -9
  59. waldiez/exporting/flow/utils/logging.py +2 -2
  60. waldiez/exporting/models/exporter.py +8 -8
  61. waldiez/exporting/models/processor.py +5 -5
  62. waldiez/exporting/tools/exporter.py +2 -2
  63. waldiez/exporting/tools/processor.py +7 -4
  64. waldiez/io/__init__.py +8 -4
  65. waldiez/io/_ws.py +10 -6
  66. waldiez/io/models/constants.py +10 -10
  67. waldiez/io/models/content/audio.py +1 -0
  68. waldiez/io/models/content/base.py +20 -18
  69. waldiez/io/models/content/file.py +1 -0
  70. waldiez/io/models/content/image.py +1 -0
  71. waldiez/io/models/content/text.py +1 -0
  72. waldiez/io/models/content/video.py +1 -0
  73. waldiez/io/models/user_input.py +10 -5
  74. waldiez/io/models/user_response.py +17 -16
  75. waldiez/io/mqtt.py +18 -31
  76. waldiez/io/redis.py +18 -22
  77. waldiez/io/structured.py +52 -53
  78. waldiez/io/utils.py +3 -0
  79. waldiez/io/ws.py +5 -1
  80. waldiez/logger.py +16 -3
  81. waldiez/models/agents/__init__.py +3 -0
  82. waldiez/models/agents/agent/agent.py +23 -16
  83. waldiez/models/agents/agent/agent_data.py +25 -22
  84. waldiez/models/agents/agent/code_execution.py +9 -11
  85. waldiez/models/agents/agent/termination_message.py +10 -12
  86. waldiez/models/agents/agent/update_system_message.py +2 -4
  87. waldiez/models/agents/agents.py +8 -8
  88. waldiez/models/agents/assistant/assistant.py +6 -3
  89. waldiez/models/agents/assistant/assistant_data.py +2 -2
  90. waldiez/models/agents/captain/captain_agent.py +7 -4
  91. waldiez/models/agents/captain/captain_agent_data.py +5 -7
  92. waldiez/models/agents/doc_agent/doc_agent.py +7 -4
  93. waldiez/models/agents/doc_agent/doc_agent_data.py +9 -10
  94. waldiez/models/agents/doc_agent/rag_query_engine.py +10 -12
  95. waldiez/models/agents/extra_requirements.py +3 -3
  96. waldiez/models/agents/group_manager/group_manager.py +12 -7
  97. waldiez/models/agents/group_manager/group_manager_data.py +13 -12
  98. waldiez/models/agents/group_manager/speakers.py +17 -19
  99. waldiez/models/agents/rag_user_proxy/rag_user_proxy.py +7 -4
  100. waldiez/models/agents/rag_user_proxy/rag_user_proxy_data.py +4 -1
  101. waldiez/models/agents/rag_user_proxy/retrieve_config.py +69 -63
  102. waldiez/models/agents/rag_user_proxy/vector_db_config.py +19 -19
  103. waldiez/models/agents/reasoning/reasoning_agent.py +7 -4
  104. waldiez/models/agents/reasoning/reasoning_agent_data.py +3 -2
  105. waldiez/models/agents/reasoning/reasoning_agent_reason_config.py +8 -8
  106. waldiez/models/agents/user_proxy/user_proxy.py +6 -3
  107. waldiez/models/agents/user_proxy/user_proxy_data.py +1 -1
  108. waldiez/models/chat/chat.py +27 -20
  109. waldiez/models/chat/chat_data.py +22 -19
  110. waldiez/models/chat/chat_message.py +9 -9
  111. waldiez/models/chat/chat_nested.py +9 -9
  112. waldiez/models/chat/chat_summary.py +6 -6
  113. waldiez/models/common/__init__.py +2 -0
  114. waldiez/models/common/ag2_version.py +2 -0
  115. waldiez/models/common/dict_utils.py +8 -6
  116. waldiez/models/common/handoff.py +18 -17
  117. waldiez/models/common/method_utils.py +7 -7
  118. waldiez/models/common/naming.py +49 -0
  119. waldiez/models/flow/flow.py +11 -6
  120. waldiez/models/flow/flow_data.py +23 -17
  121. waldiez/models/flow/info.py +3 -3
  122. waldiez/models/flow/naming.py +2 -1
  123. waldiez/models/model/_aws.py +11 -13
  124. waldiez/models/model/_llm.py +5 -0
  125. waldiez/models/model/_price.py +2 -4
  126. waldiez/models/model/extra_requirements.py +1 -3
  127. waldiez/models/model/model.py +2 -2
  128. waldiez/models/model/model_data.py +21 -21
  129. waldiez/models/tool/extra_requirements.py +2 -4
  130. waldiez/models/tool/predefined/_duckduckgo.py +1 -0
  131. waldiez/models/tool/predefined/_email.py +1 -0
  132. waldiez/models/tool/predefined/_google.py +1 -0
  133. waldiez/models/tool/predefined/_perplexity.py +1 -0
  134. waldiez/models/tool/predefined/_searxng.py +1 -0
  135. waldiez/models/tool/predefined/_tavily.py +1 -0
  136. waldiez/models/tool/predefined/_wikipedia.py +1 -0
  137. waldiez/models/tool/predefined/_youtube.py +1 -0
  138. waldiez/models/tool/tool.py +8 -5
  139. waldiez/models/tool/tool_data.py +2 -2
  140. waldiez/models/waldiez.py +152 -4
  141. waldiez/runner.py +11 -5
  142. waldiez/running/async_utils.py +192 -0
  143. waldiez/running/base_runner.py +117 -264
  144. waldiez/running/dir_utils.py +52 -0
  145. waldiez/running/environment.py +10 -44
  146. waldiez/running/events_mixin.py +252 -0
  147. waldiez/running/exceptions.py +20 -0
  148. waldiez/running/gen_seq_diagram.py +18 -15
  149. waldiez/running/io_utils.py +216 -0
  150. waldiez/running/protocol.py +11 -5
  151. waldiez/running/requirements_mixin.py +65 -0
  152. waldiez/running/results_mixin.py +926 -0
  153. waldiez/running/standard_runner.py +22 -25
  154. waldiez/running/step_by_step/breakpoints_mixin.py +192 -60
  155. waldiez/running/step_by_step/command_handler.py +3 -0
  156. waldiez/running/step_by_step/events_processor.py +194 -14
  157. waldiez/running/step_by_step/step_by_step_models.py +110 -43
  158. waldiez/running/step_by_step/step_by_step_runner.py +107 -57
  159. waldiez/running/subprocess_runner/__base__.py +9 -1
  160. waldiez/running/subprocess_runner/_async_runner.py +5 -3
  161. waldiez/running/subprocess_runner/_sync_runner.py +6 -2
  162. waldiez/running/subprocess_runner/runner.py +39 -23
  163. waldiez/running/timeline_processor.py +1 -1
  164. waldiez/utils/__init__.py +2 -0
  165. waldiez/utils/conflict_checker.py +4 -4
  166. waldiez/utils/python_manager.py +415 -0
  167. waldiez/ws/_file_handler.py +18 -18
  168. waldiez/ws/_mock.py +2 -1
  169. waldiez/ws/cli.py +36 -12
  170. waldiez/ws/client_manager.py +35 -27
  171. waldiez/ws/errors.py +3 -0
  172. waldiez/ws/models.py +43 -52
  173. waldiez/ws/reloader.py +12 -4
  174. waldiez/ws/server.py +85 -55
  175. waldiez/ws/session_manager.py +8 -9
  176. waldiez/ws/session_stats.py +1 -1
  177. waldiez/ws/utils.py +4 -1
  178. {waldiez-0.6.0.dist-info → waldiez-0.6.1.dist-info}/METADATA +82 -93
  179. waldiez-0.6.1.dist-info/RECORD +254 -0
  180. waldiez/running/post_run.py +0 -186
  181. waldiez/running/pre_run.py +0 -281
  182. waldiez/running/run_results.py +0 -14
  183. waldiez/running/utils.py +0 -625
  184. waldiez-0.6.0.dist-info/RECORD +0 -251
  185. {waldiez-0.6.0.dist-info → waldiez-0.6.1.dist-info}/WHEEL +0 -0
  186. {waldiez-0.6.0.dist-info → waldiez-0.6.1.dist-info}/entry_points.txt +0 -0
  187. {waldiez-0.6.0.dist-info → waldiez-0.6.1.dist-info}/licenses/LICENSE +0 -0
  188. {waldiez-0.6.0.dist-info → waldiez-0.6.1.dist-info}/licenses/NOTICE.md +0 -0
@@ -2,11 +2,10 @@
2
2
  # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
3
 
4
4
  # pyright: reportUnknownMemberType=false, reportAttributeAccessIssue=false
5
- # pyright: reportUnknownArgumentType=false
5
+ # pyright: reportUnknownArgumentType=false, reportUnusedParameter=false
6
6
  """Base runner for Waldiez workflows."""
7
7
 
8
8
  import importlib.util
9
- import inspect
10
9
  import json
11
10
  import shutil
12
11
  import sys
@@ -14,72 +13,35 @@ import tempfile
14
13
  import threading
15
14
  from pathlib import Path
16
15
  from types import ModuleType, TracebackType
17
- from typing import TYPE_CHECKING, Any, Callable, Coroutine, Type, Union
16
+ from typing import Any
18
17
 
19
18
  from aiofiles.os import wrap
20
19
  from anyio.from_thread import start_blocking_portal
21
- from typing_extensions import Self
20
+ from typing_extensions import Self, override
22
21
 
23
22
  from waldiez.exporter import WaldiezExporter
24
23
  from waldiez.logger import WaldiezLogger, get_logger
25
24
  from waldiez.models import Waldiez
26
25
 
27
- from .environment import refresh_environment, reset_env_vars, set_env_vars
26
+ from .dir_utils import a_chdir, chdir
27
+ from .environment import reset_env_vars, set_env_vars
28
+ from .events_mixin import EventsMixin
28
29
  from .exceptions import StopRunningException
29
- from .post_run import after_run
30
- from .pre_run import RequirementsMixin, dump_waldiez
31
30
  from .protocol import WaldiezRunnerProtocol
32
- from .utils import (
33
- a_chdir,
34
- chdir,
35
- input_async,
36
- input_sync,
37
- is_async_callable,
38
- syncify,
39
- )
40
-
41
- if TYPE_CHECKING:
42
- # pylint: disable=line-too-long
43
- from autogen.events import ( # type: ignore[import-untyped,import-not-found,unused-ignore] # noqa: E501
44
- BaseEvent,
45
- )
46
- from autogen.messages import ( # type: ignore[import-untyped,import-not-found,unused-ignore] # noqa: E501
47
- BaseMessage,
48
- )
31
+ from .requirements_mixin import RequirementsMixin
32
+ from .results_mixin import ResultsMixin
49
33
 
50
34
 
51
35
  # pylint: disable=too-many-public-methods
52
- class WaldiezBaseRunner(WaldiezRunnerProtocol, RequirementsMixin):
53
- """Base runner for Waldiez.
54
-
55
- Initialization parameters:
56
- - waldiez: The Waldiez flow to run.
57
- - output_path: Path to save the output file.
58
- - uploads_root: Root directory for uploads.
59
- - structured_io: Whether to use structured I/O.
60
- - dot_env: Path to a .env file for environment variables.
61
-
62
- Methods to possibly override:
63
- - prepare: Prepare the environment and paths for running the flow.
64
- - _before_run: Actions to perform before running the flow.
65
- - a_prepare: Async version of the prepare method.
66
- - _a_before_run: Async actions to perform before running the flow.
67
- - _run: Actual implementation of the run logic.
68
- - _a_run: Async implementation of the run logic.
69
- - _after_run: Actions to perform after running the flow.
70
- - _a_after_run: Async actions to perform after running the flow.
71
- """
36
+ class WaldiezBaseRunner(WaldiezRunnerProtocol, RequirementsMixin, ResultsMixin):
37
+ """Base runner for Waldiez."""
72
38
 
73
39
  _structured_io: bool
74
40
  _output_path: str | Path | None
75
41
  _uploads_root: str | Path | None
76
42
  _dot_env_path: str | Path | None
77
43
  _running: bool
78
- _is_async: bool
79
44
  _waldiez_file: Path
80
- _input: Callable[..., str] | Callable[..., Coroutine[Any, Any, str]]
81
- _print: Callable[..., None]
82
- _send: Callable[[Union["BaseEvent", "BaseMessage"]], None]
83
45
 
84
46
  def __init__(
85
47
  self,
@@ -96,10 +58,11 @@ class WaldiezBaseRunner(WaldiezRunnerProtocol, RequirementsMixin):
96
58
  WaldiezBaseRunner._output_path = output_path
97
59
  WaldiezBaseRunner._uploads_root = uploads_root
98
60
  WaldiezBaseRunner._dot_env_path = dot_env
99
- WaldiezBaseRunner._input = input
100
- WaldiezBaseRunner._print = print
101
- WaldiezBaseRunner._send = print
102
- WaldiezBaseRunner._is_async = waldiez.is_async
61
+ EventsMixin.set_input_function(input)
62
+ EventsMixin.set_print_function(print)
63
+ EventsMixin.set_send_function(print)
64
+ EventsMixin.set_async(waldiez.is_async)
65
+ RequirementsMixin.__init__(self)
103
66
  self._waldiez = waldiez
104
67
  self._called_install_requirements = False
105
68
  self._exporter = WaldiezExporter(waldiez)
@@ -119,14 +82,15 @@ class WaldiezBaseRunner(WaldiezRunnerProtocol, RequirementsMixin):
119
82
  elif isinstance(waldiez_file, Path):
120
83
  waldiez_file_path = waldiez_file.resolve()
121
84
  else:
122
- waldiez_file_path = dump_waldiez(waldiez, output_path=output_path)
85
+ waldiez_file_path = waldiez.dump(to=output_path)
123
86
  if not waldiez_file_path or not waldiez_file_path.is_file():
124
87
  raise ValueError("Could not resolve a waldiez file path")
125
88
  WaldiezBaseRunner._waldiez_file = waldiez_file_path
126
89
 
90
+ @override
127
91
  @staticmethod
128
92
  def print(*args: Any, **kwargs: Any) -> None:
129
- """Print a message to the console.
93
+ """Print a message to the output stream.
130
94
 
131
95
  Parameters
132
96
  ----------
@@ -137,10 +101,11 @@ class WaldiezBaseRunner(WaldiezRunnerProtocol, RequirementsMixin):
137
101
  """
138
102
  if len(args) == 1 and isinstance(args[0], dict):
139
103
  arg = json.dumps(args[0], default=str, ensure_ascii=False)
140
- WaldiezBaseRunner._print(arg, **kwargs)
104
+ EventsMixin.do_print(arg, **kwargs)
141
105
  else:
142
- WaldiezBaseRunner._print(*args, **kwargs)
106
+ EventsMixin.do_print(*args, **kwargs)
143
107
 
108
+ @override
144
109
  def is_running(self) -> bool:
145
110
  """Check if the workflow is currently running.
146
111
 
@@ -152,99 +117,6 @@ class WaldiezBaseRunner(WaldiezRunnerProtocol, RequirementsMixin):
152
117
  with self._running_lock:
153
118
  return WaldiezBaseRunner._running
154
119
 
155
- @staticmethod
156
- def get_input_function() -> (
157
- Callable[..., str] | Callable[..., Coroutine[Any, Any, str]]
158
- ):
159
- """Get the input function for user interaction.
160
-
161
- Returns
162
- -------
163
- Callable[[str, bool], str]
164
- A function that takes a prompt and a password flag,
165
- returning user input.
166
- """
167
- if hasattr(WaldiezBaseRunner, "_input") and callable(
168
- WaldiezBaseRunner._input
169
- ):
170
- return WaldiezBaseRunner._input
171
- if WaldiezBaseRunner._is_async:
172
- return input_async
173
- return input_sync
174
-
175
- @staticmethod
176
- async def a_get_user_input(
177
- prompt: str, *, password: bool = False, **kwargs: Any
178
- ) -> str:
179
- """Get user input with an optional password prompt.
180
-
181
- Parameters
182
- ----------
183
- prompt : str
184
- The prompt to display to the user.
185
- password : bool, optional
186
- If True, the input will be hidden (default is False).
187
- **kwargs : Any
188
- Additional keyword arguments to pass to the input function.
189
-
190
- Returns
191
- -------
192
- str
193
- The user input.
194
- """
195
- input_function = WaldiezBaseRunner.get_input_function()
196
- if is_async_callable(input_function):
197
- try:
198
- result = await input_function( # type: ignore
199
- prompt,
200
- password=password,
201
- **kwargs,
202
- )
203
- except TypeError:
204
- result = await input_function(prompt) # type: ignore
205
- else:
206
- try:
207
- result = input_function(prompt, password=password, **kwargs)
208
- except TypeError:
209
- result = input_function(prompt)
210
- return result # pyright: ignore
211
-
212
- @staticmethod
213
- def get_user_input(
214
- prompt: str,
215
- *,
216
- password: bool = False,
217
- **kwargs: Any,
218
- ) -> str:
219
- """Get user input with an optional password prompt.
220
-
221
- Parameters
222
- ----------
223
- prompt : str
224
- The prompt to display to the user.
225
- password : bool, optional
226
- If True, the input will be hidden (default is False).
227
- **kwargs : Any
228
- Additional keyword arguments to pass to the input function.
229
-
230
- Returns
231
- -------
232
- str
233
- The user input.
234
- """
235
- input_function = WaldiezBaseRunner.get_input_function()
236
- if inspect.iscoroutinefunction(input_function):
237
- try:
238
- return syncify(input_function)(
239
- prompt, password=password, **kwargs
240
- )
241
- except TypeError:
242
- return syncify(input_function)(prompt)
243
- try:
244
- return str(input_function(prompt, password=password, **kwargs))
245
- except TypeError:
246
- return str(input_function(prompt))
247
-
248
120
  def _load_module(self, output_file: Path, temp_dir: Path) -> ModuleType:
249
121
  """Load the module from the waldiez file."""
250
122
  file_name = output_file.name
@@ -270,7 +142,7 @@ class WaldiezBaseRunner(WaldiezRunnerProtocol, RequirementsMixin):
270
142
  ) -> Path:
271
143
  """Run before the flow execution."""
272
144
  self.log.info("Preparing workflow file: %s", output_file)
273
- temp_dir = Path(tempfile.mkdtemp())
145
+ temp_dir = Path(tempfile.mkdtemp(prefix="wlz-"))
274
146
  file_name = output_file.name
275
147
  with chdir(to=temp_dir):
276
148
  self._exporter.export(
@@ -340,13 +212,14 @@ class WaldiezBaseRunner(WaldiezRunnerProtocol, RequirementsMixin):
340
212
  def _after_run(
341
213
  self,
342
214
  results: list[dict[str, Any]],
215
+ error: BaseException | None,
343
216
  output_file: Path,
344
217
  waldiez_file: Path,
345
218
  uploads_root: Path | None,
346
219
  temp_dir: Path,
347
220
  skip_mmd: bool,
348
221
  skip_timeline: bool,
349
- ) -> None:
222
+ ) -> Path | None:
350
223
  """Run after the flow execution."""
351
224
  # Save results
352
225
  self._last_results = results
@@ -355,7 +228,9 @@ class WaldiezBaseRunner(WaldiezRunnerProtocol, RequirementsMixin):
355
228
  self._stop_requested.clear()
356
229
  # pylint: disable=broad-exception-caught
357
230
  try:
358
- after_run(
231
+ return ResultsMixin.post_run(
232
+ results=results,
233
+ error=error,
359
234
  temp_dir=temp_dir,
360
235
  output_file=output_file,
361
236
  flow_name=self._waldiez.name,
@@ -367,27 +242,39 @@ class WaldiezBaseRunner(WaldiezRunnerProtocol, RequirementsMixin):
367
242
  except BaseException as exc: # pragma: no cover
368
243
  self.log.warning("Error occurred during after_run: %s", exc)
369
244
  self.log.info("Cleanup completed")
245
+ return None
370
246
 
371
247
  async def _a_after_run(
372
248
  self,
373
249
  results: list[dict[str, Any]],
250
+ error: BaseException | None,
374
251
  output_file: Path,
375
252
  waldiez_file: Path,
376
253
  uploads_root: Path | None,
377
254
  temp_dir: Path,
378
255
  skip_mmd: bool,
379
256
  skip_timeline: bool,
380
- ) -> None:
257
+ ) -> Path | None:
381
258
  """Run after the flow execution asynchronously."""
382
- self._after_run(
383
- results=results,
384
- output_file=output_file,
385
- uploads_root=uploads_root,
386
- temp_dir=temp_dir,
387
- skip_mmd=skip_mmd,
388
- skip_timeline=skip_timeline,
389
- waldiez_file=waldiez_file,
390
- )
259
+ self._last_results = results
260
+ self._stop_requested.clear()
261
+ # pylint: disable=broad-exception-caught
262
+ try:
263
+ return await ResultsMixin.a_post_run(
264
+ results=results,
265
+ error=error,
266
+ temp_dir=temp_dir,
267
+ output_file=output_file,
268
+ flow_name=self._waldiez.name,
269
+ waldiez_file=waldiez_file,
270
+ uploads_root=uploads_root,
271
+ skip_mmd=skip_mmd,
272
+ skip_timeline=skip_timeline,
273
+ )
274
+ except BaseException as exc: # pragma: no cover
275
+ self.log.warning("Error occurred during a_after_run: %s", exc)
276
+ self.log.info("Cleanup completed")
277
+ return None
391
278
 
392
279
  @staticmethod
393
280
  def _prepare_paths(
@@ -408,68 +295,7 @@ class WaldiezBaseRunner(WaldiezRunnerProtocol, RequirementsMixin):
408
295
  output_file: Path = Path(WaldiezBaseRunner._output_path)
409
296
  return output_file, uploads_root_path
410
297
 
411
- @staticmethod
412
- async def a_process_event(
413
- event: Union["BaseEvent", "BaseMessage"],
414
- skip_send: bool = False,
415
- ) -> None:
416
- """Process an event or message asynchronously.
417
-
418
- Parameters
419
- ----------
420
- event : Union[BaseEvent, BaseMessage]
421
- The event or message to process.
422
- skip_send : bool
423
- Skip sending the event.
424
- """
425
- if hasattr(event, "type"): # pragma: no branch
426
- if event.type == "input_request":
427
- prompt = getattr(
428
- event, "prompt", getattr(event.content, "prompt", "> ")
429
- )
430
- password = getattr(
431
- event,
432
- "password",
433
- getattr(event.content, "password", False),
434
- )
435
- user_input = await WaldiezBaseRunner.a_get_user_input(
436
- prompt, password=password
437
- )
438
- await event.content.respond(user_input)
439
- elif not skip_send:
440
- WaldiezBaseRunner._send(event)
441
-
442
- @staticmethod
443
- def process_event(
444
- event: Union["BaseEvent", "BaseMessage"],
445
- skip_send: bool = False,
446
- ) -> None:
447
- """Process an event or message synchronously.
448
-
449
- Parameters
450
- ----------
451
- event : Union[BaseEvent, BaseMessage]
452
- The event or message to process.
453
- skip_send : bool
454
- Skip sending the event.
455
- """
456
- if hasattr(event, "type"): # pragma: no branch
457
- if event.type == "input_request":
458
- prompt = getattr(
459
- event, "prompt", getattr(event.content, "prompt", "> ")
460
- )
461
- password = getattr(
462
- event,
463
- "password",
464
- getattr(event.content, "password", False),
465
- )
466
- user_input = WaldiezBaseRunner.get_user_input(
467
- prompt, password=password
468
- )
469
- event.content.respond(user_input)
470
- elif not skip_send:
471
- WaldiezBaseRunner._send(event)
472
-
298
+ @override
473
299
  def before_run(
474
300
  self,
475
301
  output_file: Path,
@@ -494,6 +320,7 @@ class WaldiezBaseRunner(WaldiezRunnerProtocol, RequirementsMixin):
494
320
  uploads_root=uploads_root,
495
321
  )
496
322
 
323
+ @override
497
324
  async def a_before_run(
498
325
  self,
499
326
  output_file: Path,
@@ -549,11 +376,11 @@ class WaldiezBaseRunner(WaldiezRunnerProtocol, RequirementsMixin):
549
376
  uploads_root=uploads_root_path,
550
377
  )
551
378
  self.install_requirements()
552
- refresh_environment()
553
379
  return temp_dir, output_file, uploads_root_path
554
380
 
555
381
  # noinspection PyProtocol
556
382
  # pylint: disable=too-many-locals,unused-argument
383
+ @override
557
384
  def run(
558
385
  self,
559
386
  output_path: str | Path | None = None,
@@ -618,8 +445,10 @@ class WaldiezBaseRunner(WaldiezRunnerProtocol, RequirementsMixin):
618
445
  uploads_root=uploads_root,
619
446
  )
620
447
  WaldiezBaseRunner._running = True
621
- results: list[dict[str, Any]]
448
+ results: list[dict[str, Any]] = []
449
+ error: BaseException | None = None
622
450
  old_env_vars = set_env_vars(self._waldiez.get_flow_env_vars())
451
+ output_dir = output_file.parent
623
452
  try:
624
453
  with chdir(to=temp_dir):
625
454
  sys.path.insert(0, str(temp_dir))
@@ -631,25 +460,30 @@ class WaldiezBaseRunner(WaldiezRunnerProtocol, RequirementsMixin):
631
460
  skip_timeline=skip_timeline,
632
461
  )
633
462
  except (SystemExit, StopRunningException, KeyboardInterrupt) as exc:
463
+ error = exc
464
+ self.log.warning("Execution stopped: %s", exc)
634
465
  raise StopRunningException(StopRunningException.reason) from exc
635
466
  except BaseException as exc: # pylint: disable=broad-exception-caught
636
467
  self.log.error("Error occurred while running workflow: %s", exc)
637
- results = [{"error": str(exc)}]
468
+ error = exc
638
469
  finally:
639
470
  WaldiezBaseRunner._running = False
640
471
  reset_env_vars(old_env_vars)
641
- self.after_run(
642
- results=results,
643
- output_file=output_file,
644
- uploads_root=uploads_root_path,
645
- temp_dir=temp_dir,
646
- skip_mmd=skip_mmd,
647
- skip_timeline=skip_timeline,
648
- )
649
- self._print("<Waldiez> - Done running the flow.")
472
+ output = self.after_run(
473
+ results=results,
474
+ error=error,
475
+ output_file=output_file,
476
+ uploads_root=uploads_root_path,
477
+ temp_dir=temp_dir,
478
+ skip_mmd=skip_mmd,
479
+ skip_timeline=skip_timeline,
480
+ )
481
+ if output:
482
+ output_dir = output
483
+ EventsMixin.do_print("<Waldiez> - Done running the flow.")
650
484
  if sys.path[0] == str(temp_dir):
651
485
  sys.path.pop(0)
652
- return results
486
+ return self.get_results(results, output_dir)
653
487
 
654
488
  async def a_prepare(
655
489
  self,
@@ -679,11 +513,11 @@ class WaldiezBaseRunner(WaldiezRunnerProtocol, RequirementsMixin):
679
513
  uploads_root=uploads_root_path,
680
514
  )
681
515
  await self.a_install_requirements()
682
- refresh_environment()
683
516
  return temp_dir, output_file, uploads_root_path
684
517
 
685
518
  # noinspection DuplicatedCode
686
519
  # noinspection PyProtocol
520
+ @override
687
521
  async def a_run(
688
522
  self,
689
523
  output_path: str | Path | None = None,
@@ -739,7 +573,9 @@ class WaldiezBaseRunner(WaldiezRunnerProtocol, RequirementsMixin):
739
573
  uploads_root=uploads_root,
740
574
  )
741
575
  WaldiezBaseRunner._running = True
742
- results: list[dict[str, Any]]
576
+ results: list[dict[str, Any]] = []
577
+ error: BaseException | None = None
578
+ output_dir = output_file.parent
743
579
  old_env_vars = set_env_vars(self._waldiez.get_flow_env_vars())
744
580
  try:
745
581
  async with a_chdir(to=temp_dir):
@@ -752,40 +588,49 @@ class WaldiezBaseRunner(WaldiezRunnerProtocol, RequirementsMixin):
752
588
  skip_timeline=skip_timeline,
753
589
  )
754
590
  except (SystemExit, StopRunningException, KeyboardInterrupt) as exc:
591
+ self.log.warning("Execution stopped: %s", exc)
592
+ error = exc
755
593
  raise StopRunningException(StopRunningException.reason) from exc
756
594
  except BaseException as exc: # pylint: disable=broad-exception-caught
757
- results = [{"error": str(exc)}]
595
+ self.log.error("Error occurred while running workflow: %s", exc)
596
+ error = exc
758
597
  finally:
759
598
  WaldiezBaseRunner._running = False
760
599
  reset_env_vars(old_env_vars)
761
- await self._a_after_run(
762
- results=results,
763
- output_file=output_file,
764
- uploads_root=uploads_root_path,
765
- waldiez_file=WaldiezBaseRunner._waldiez_file,
766
- temp_dir=temp_dir,
767
- skip_mmd=skip_mmd,
768
- skip_timeline=skip_timeline,
769
- )
600
+ output = await self.a_after_run(
601
+ results=results,
602
+ error=error,
603
+ output_file=output_file,
604
+ uploads_root=uploads_root_path,
605
+ temp_dir=temp_dir,
606
+ skip_mmd=skip_mmd,
607
+ skip_timeline=skip_timeline,
608
+ )
609
+ if output:
610
+ output_dir = output
770
611
  if sys.path[0] == str(temp_dir):
771
612
  sys.path.pop(0)
772
- return results
613
+ return await self.a_get_results(results, output_dir)
773
614
 
615
+ @override
774
616
  def after_run(
775
617
  self,
776
618
  results: list[dict[str, Any]],
619
+ error: BaseException | None,
777
620
  output_file: Path,
778
621
  uploads_root: Path | None,
779
622
  temp_dir: Path,
780
623
  skip_mmd: bool,
781
624
  skip_timeline: bool,
782
- ) -> None:
625
+ ) -> Path | None:
783
626
  """Actions to perform after running the flow.
784
627
 
785
628
  Parameters
786
629
  ----------
787
630
  results : list[dict[str, Any]]
788
631
  The results of the flow run.
632
+ error : BaseException | None
633
+ Optional error during the run.
789
634
  output_file : Path
790
635
  The path to the output file.
791
636
  uploads_root : Path | None
@@ -796,9 +641,15 @@ class WaldiezBaseRunner(WaldiezRunnerProtocol, RequirementsMixin):
796
641
  Whether to skip generating the mermaid diagram.
797
642
  skip_timeline : bool
798
643
  Whether to skip generating the timeline JSON.
644
+
645
+ Returns
646
+ -------
647
+ Path | None
648
+ The destination directory if output file, else None
799
649
  """
800
- self._after_run(
650
+ return self._after_run(
801
651
  results=results,
652
+ error=error,
802
653
  output_file=output_file,
803
654
  waldiez_file=WaldiezBaseRunner._waldiez_file,
804
655
  uploads_root=uploads_root,
@@ -807,21 +658,25 @@ class WaldiezBaseRunner(WaldiezRunnerProtocol, RequirementsMixin):
807
658
  skip_timeline=skip_timeline,
808
659
  )
809
660
 
661
+ @override
810
662
  async def a_after_run(
811
663
  self,
812
664
  results: list[dict[str, Any]],
665
+ error: BaseException | None,
813
666
  output_file: Path,
814
667
  uploads_root: Path | None,
815
668
  temp_dir: Path,
816
669
  skip_mmd: bool,
817
670
  skip_timeline: bool,
818
- ) -> None:
671
+ ) -> Path | None:
819
672
  """Asynchronously perform actions after running the flow.
820
673
 
821
674
  Parameters
822
675
  ----------
823
676
  results : list[dict[str, Any]]
824
677
  The results of the flow run.
678
+ error : BaseException | None
679
+ Optional error during the run.
825
680
  output_file : Path
826
681
  The path to the output file.
827
682
  uploads_root : Path | None
@@ -832,9 +687,14 @@ class WaldiezBaseRunner(WaldiezRunnerProtocol, RequirementsMixin):
832
687
  Whether to skip generating the mermaid diagram.
833
688
  skip_timeline : bool
834
689
 
690
+ Returns
691
+ -------
692
+ Path | None
693
+ The destination directory if output file, else None
835
694
  """
836
- await self._a_after_run(
695
+ return await self._a_after_run(
837
696
  results=results,
697
+ error=error,
838
698
  output_file=output_file,
839
699
  uploads_root=uploads_root,
840
700
  temp_dir=temp_dir,
@@ -946,14 +806,7 @@ class WaldiezBaseRunner(WaldiezRunnerProtocol, RequirementsMixin):
946
806
  )
947
807
 
948
808
  def stop(self) -> None:
949
- """Stop the workflow execution.
950
-
951
- This method sets the stop flag that will be checked by the event
952
- handlers and other parts of the workflow execution to gracefully
953
- terminate the workflow execution.
954
- Note: Stopping will occur at the "next" AutoGen event, not immediately.
955
- """
956
- self.log.info("Stop requested - setting stop flag")
809
+ """Stop the workflow execution."""
957
810
  self._stop_requested.set()
958
811
 
959
812
  def is_stop_requested(self) -> bool:
@@ -980,7 +833,7 @@ class WaldiezBaseRunner(WaldiezRunnerProtocol, RequirementsMixin):
980
833
 
981
834
  def __exit__(
982
835
  self,
983
- exc_type: Type[BaseException],
836
+ exc_type: type[BaseException],
984
837
  exc_value: BaseException,
985
838
  traceback: TracebackType,
986
839
  ) -> None:
@@ -990,7 +843,7 @@ class WaldiezBaseRunner(WaldiezRunnerProtocol, RequirementsMixin):
990
843
 
991
844
  async def __aexit__(
992
845
  self,
993
- exc_type: Type[BaseException],
846
+ exc_type: type[BaseException],
994
847
  exc_value: BaseException,
995
848
  traceback: TracebackType,
996
849
  ) -> None:
@@ -0,0 +1,52 @@
1
+ # SPDX-License-Identifier: Apache-2.0.
2
+ # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
+ """Dir related utilities for the waldiez runner."""
4
+
5
+ import os
6
+ from collections.abc import AsyncIterator, Iterator
7
+ from contextlib import asynccontextmanager, contextmanager
8
+ from pathlib import Path
9
+
10
+
11
+ @contextmanager
12
+ def chdir(to: str | Path) -> Iterator[None]:
13
+ """Change the current working directory in a context.
14
+
15
+ Parameters
16
+ ----------
17
+ to : str | Path
18
+ The directory to change to.
19
+
20
+ Yields
21
+ ------
22
+ Iterator[None]
23
+ The context manager.
24
+ """
25
+ old_cwd = str(os.getcwd())
26
+ try:
27
+ os.chdir(to)
28
+ yield
29
+ finally:
30
+ os.chdir(old_cwd)
31
+
32
+
33
+ @asynccontextmanager
34
+ async def a_chdir(to: str | Path) -> AsyncIterator[None]:
35
+ """Asynchronously change the current working directory in a context.
36
+
37
+ Parameters
38
+ ----------
39
+ to : str | Path
40
+ The directory to change to.
41
+
42
+ Yields
43
+ ------
44
+ AsyncIterator[None]
45
+ The async context manager.
46
+ """
47
+ old_cwd = str(os.getcwd())
48
+ try:
49
+ os.chdir(to)
50
+ yield
51
+ finally:
52
+ os.chdir(old_cwd)