waldiez 0.5.10__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 (192) hide show
  1. waldiez/__init__.py +1 -1
  2. waldiez/_version.py +1 -1
  3. waldiez/cli.py +19 -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 +15 -16
  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 +40 -24
  12. waldiez/exporting/agent/extras/group_member_extras.py +6 -5
  13. waldiez/exporting/agent/extras/handoffs/after_work.py +2 -1
  14. waldiez/exporting/agent/extras/handoffs/available.py +2 -1
  15. waldiez/exporting/agent/extras/handoffs/condition.py +3 -2
  16. waldiez/exporting/agent/extras/handoffs/handoff.py +2 -1
  17. waldiez/exporting/agent/extras/handoffs/target.py +7 -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/agent/termination.py +1 -0
  26. waldiez/exporting/chats/exporter.py +4 -4
  27. waldiez/exporting/chats/processor.py +1 -2
  28. waldiez/exporting/chats/utils/common.py +89 -48
  29. waldiez/exporting/chats/utils/group.py +9 -9
  30. waldiez/exporting/chats/utils/nested.py +7 -7
  31. waldiez/exporting/chats/utils/sequential.py +1 -1
  32. waldiez/exporting/chats/utils/single.py +2 -2
  33. waldiez/exporting/core/constants.py +3 -1
  34. waldiez/exporting/core/content.py +7 -7
  35. waldiez/exporting/core/context.py +5 -3
  36. waldiez/exporting/core/exporter.py +5 -3
  37. waldiez/exporting/core/exporters.py +2 -2
  38. waldiez/exporting/core/extras/agent_extras/captain_extras.py +2 -2
  39. waldiez/exporting/core/extras/agent_extras/group_manager_extras.py +2 -2
  40. waldiez/exporting/core/extras/agent_extras/rag_user_extras.py +2 -2
  41. waldiez/exporting/core/extras/agent_extras/standard_extras.py +3 -8
  42. waldiez/exporting/core/extras/base.py +7 -5
  43. waldiez/exporting/core/extras/flow_extras.py +4 -5
  44. waldiez/exporting/core/extras/model_extras.py +2 -2
  45. waldiez/exporting/core/extras/path_resolver.py +1 -2
  46. waldiez/exporting/core/extras/serializer.py +13 -11
  47. waldiez/exporting/core/protocols.py +6 -5
  48. waldiez/exporting/core/result.py +25 -28
  49. waldiez/exporting/core/types.py +11 -10
  50. waldiez/exporting/core/utils/llm_config.py +4 -4
  51. waldiez/exporting/core/validation.py +10 -11
  52. waldiez/exporting/flow/execution_generator.py +99 -10
  53. waldiez/exporting/flow/exporter.py +2 -2
  54. waldiez/exporting/flow/factory.py +2 -2
  55. waldiez/exporting/flow/file_generator.py +4 -2
  56. waldiez/exporting/flow/merger.py +5 -3
  57. waldiez/exporting/flow/orchestrator.py +72 -2
  58. waldiez/exporting/flow/utils/common.py +6 -6
  59. waldiez/exporting/flow/utils/importing.py +7 -8
  60. waldiez/exporting/flow/utils/linting.py +25 -9
  61. waldiez/exporting/flow/utils/logging.py +5 -77
  62. waldiez/exporting/models/exporter.py +8 -8
  63. waldiez/exporting/models/processor.py +5 -5
  64. waldiez/exporting/tools/exporter.py +2 -2
  65. waldiez/exporting/tools/processor.py +7 -4
  66. waldiez/io/__init__.py +11 -5
  67. waldiez/io/_ws.py +12 -6
  68. waldiez/io/models/constants.py +10 -10
  69. waldiez/io/models/content/audio.py +1 -0
  70. waldiez/io/models/content/base.py +20 -18
  71. waldiez/io/models/content/file.py +1 -0
  72. waldiez/io/models/content/image.py +1 -0
  73. waldiez/io/models/content/text.py +1 -0
  74. waldiez/io/models/content/video.py +1 -0
  75. waldiez/io/models/user_input.py +10 -5
  76. waldiez/io/models/user_response.py +17 -16
  77. waldiez/io/mqtt.py +18 -31
  78. waldiez/io/redis.py +18 -22
  79. waldiez/io/structured.py +122 -70
  80. waldiez/io/utils.py +19 -10
  81. waldiez/io/ws.py +7 -3
  82. waldiez/logger.py +16 -3
  83. waldiez/models/agents/__init__.py +3 -0
  84. waldiez/models/agents/agent/agent.py +25 -17
  85. waldiez/models/agents/agent/agent_data.py +25 -22
  86. waldiez/models/agents/agent/code_execution.py +9 -11
  87. waldiez/models/agents/agent/termination_message.py +10 -12
  88. waldiez/models/agents/agent/update_system_message.py +2 -4
  89. waldiez/models/agents/agents.py +8 -8
  90. waldiez/models/agents/assistant/assistant.py +6 -3
  91. waldiez/models/agents/assistant/assistant_data.py +2 -2
  92. waldiez/models/agents/captain/captain_agent.py +7 -4
  93. waldiez/models/agents/captain/captain_agent_data.py +5 -7
  94. waldiez/models/agents/doc_agent/doc_agent.py +7 -4
  95. waldiez/models/agents/doc_agent/doc_agent_data.py +9 -10
  96. waldiez/models/agents/doc_agent/rag_query_engine.py +10 -12
  97. waldiez/models/agents/extra_requirements.py +3 -3
  98. waldiez/models/agents/group_manager/group_manager.py +12 -7
  99. waldiez/models/agents/group_manager/group_manager_data.py +13 -12
  100. waldiez/models/agents/group_manager/speakers.py +17 -19
  101. waldiez/models/agents/rag_user_proxy/rag_user_proxy.py +7 -4
  102. waldiez/models/agents/rag_user_proxy/rag_user_proxy_data.py +4 -1
  103. waldiez/models/agents/rag_user_proxy/retrieve_config.py +69 -63
  104. waldiez/models/agents/rag_user_proxy/vector_db_config.py +19 -19
  105. waldiez/models/agents/reasoning/reasoning_agent.py +7 -4
  106. waldiez/models/agents/reasoning/reasoning_agent_data.py +3 -2
  107. waldiez/models/agents/reasoning/reasoning_agent_reason_config.py +8 -8
  108. waldiez/models/agents/user_proxy/user_proxy.py +6 -3
  109. waldiez/models/agents/user_proxy/user_proxy_data.py +1 -1
  110. waldiez/models/chat/chat.py +28 -20
  111. waldiez/models/chat/chat_data.py +22 -21
  112. waldiez/models/chat/chat_message.py +9 -9
  113. waldiez/models/chat/chat_nested.py +9 -9
  114. waldiez/models/chat/chat_summary.py +6 -6
  115. waldiez/models/common/__init__.py +2 -0
  116. waldiez/models/common/ag2_version.py +2 -0
  117. waldiez/models/common/base.py +2 -0
  118. waldiez/models/common/dict_utils.py +8 -6
  119. waldiez/models/common/handoff.py +20 -17
  120. waldiez/models/common/method_utils.py +9 -7
  121. waldiez/models/common/naming.py +49 -0
  122. waldiez/models/flow/flow.py +11 -6
  123. waldiez/models/flow/flow_data.py +23 -17
  124. waldiez/models/flow/info.py +3 -3
  125. waldiez/models/flow/naming.py +2 -1
  126. waldiez/models/model/_aws.py +11 -13
  127. waldiez/models/model/_llm.py +8 -0
  128. waldiez/models/model/_price.py +2 -4
  129. waldiez/models/model/extra_requirements.py +1 -3
  130. waldiez/models/model/model.py +2 -2
  131. waldiez/models/model/model_data.py +21 -21
  132. waldiez/models/tool/extra_requirements.py +2 -4
  133. waldiez/models/tool/predefined/_duckduckgo.py +1 -0
  134. waldiez/models/tool/predefined/_email.py +4 -0
  135. waldiez/models/tool/predefined/_google.py +1 -0
  136. waldiez/models/tool/predefined/_perplexity.py +2 -1
  137. waldiez/models/tool/predefined/_searxng.py +2 -1
  138. waldiez/models/tool/predefined/_tavily.py +1 -0
  139. waldiez/models/tool/predefined/_wikipedia.py +2 -1
  140. waldiez/models/tool/predefined/_youtube.py +1 -0
  141. waldiez/models/tool/tool.py +8 -5
  142. waldiez/models/tool/tool_data.py +2 -2
  143. waldiez/models/waldiez.py +152 -4
  144. waldiez/runner.py +11 -5
  145. waldiez/running/async_utils.py +192 -0
  146. waldiez/running/base_runner.py +155 -241
  147. waldiez/running/dir_utils.py +52 -0
  148. waldiez/running/environment.py +10 -44
  149. waldiez/running/events_mixin.py +252 -0
  150. waldiez/running/exceptions.py +20 -0
  151. waldiez/running/gen_seq_diagram.py +18 -15
  152. waldiez/running/io_utils.py +216 -0
  153. waldiez/running/protocol.py +11 -5
  154. waldiez/running/requirements_mixin.py +65 -0
  155. waldiez/running/results_mixin.py +926 -0
  156. waldiez/running/standard_runner.py +24 -27
  157. waldiez/running/step_by_step/breakpoints_mixin.py +503 -47
  158. waldiez/running/step_by_step/command_handler.py +154 -0
  159. waldiez/running/step_by_step/events_processor.py +379 -0
  160. waldiez/running/step_by_step/step_by_step_models.py +425 -41
  161. waldiez/running/step_by_step/step_by_step_runner.py +437 -382
  162. waldiez/running/subprocess_runner/__base__.py +13 -8
  163. waldiez/running/subprocess_runner/_async_runner.py +6 -4
  164. waldiez/running/subprocess_runner/_sync_runner.py +11 -6
  165. waldiez/running/subprocess_runner/runner.py +48 -23
  166. waldiez/running/timeline_processor.py +1 -1
  167. waldiez/utils/__init__.py +2 -0
  168. waldiez/utils/conflict_checker.py +4 -4
  169. waldiez/utils/python_manager.py +415 -0
  170. waldiez/ws/__init__.py +8 -7
  171. waldiez/ws/_file_handler.py +18 -20
  172. waldiez/ws/_mock.py +75 -0
  173. waldiez/ws/cli.py +58 -10
  174. waldiez/ws/client_manager.py +77 -53
  175. waldiez/ws/errors.py +3 -0
  176. waldiez/ws/models.py +61 -53
  177. waldiez/ws/reloader.py +33 -4
  178. waldiez/ws/server.py +121 -52
  179. waldiez/ws/session_manager.py +8 -9
  180. waldiez/ws/session_stats.py +1 -1
  181. waldiez/ws/utils.py +33 -5
  182. {waldiez-0.5.10.dist-info → waldiez-0.6.1.dist-info}/METADATA +107 -109
  183. waldiez-0.6.1.dist-info/RECORD +254 -0
  184. waldiez/running/post_run.py +0 -180
  185. waldiez/running/pre_run.py +0 -159
  186. waldiez/running/run_results.py +0 -14
  187. waldiez/running/utils.py +0 -511
  188. waldiez-0.5.10.dist-info/RECORD +0 -248
  189. {waldiez-0.5.10.dist-info → waldiez-0.6.1.dist-info}/WHEEL +0 -0
  190. {waldiez-0.5.10.dist-info → waldiez-0.6.1.dist-info}/entry_points.txt +0 -0
  191. {waldiez-0.5.10.dist-info → waldiez-0.6.1.dist-info}/licenses/LICENSE +0 -0
  192. {waldiez-0.5.10.dist-info → waldiez-0.6.1.dist-info}/licenses/NOTICE.md +0 -0
@@ -1,10 +1,11 @@
1
1
  # SPDX-License-Identifier: Apache-2.0.
2
2
  # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
3
 
4
- # pylint: disable=line-too-long
4
+ # pylint: disable=duplicate-code,line-too-long
5
5
  # pyright: reportUnknownMemberType=false, reportAttributeAccessIssue=false
6
6
  # pyright: reportUnknownArgumentType=false, reportOptionalMemberAccess=false
7
- # pylint: disable=duplicate-code
7
+ # pyright: reportDeprecated=false, reportMissingTypeStubs=false
8
+ # pyright: reportUnsafeMultipleInheritance=false
8
9
  # flake8: noqa: E501
9
10
 
10
11
  """Step-by-step Waldiez runner with user interaction capabilities."""
@@ -13,20 +14,26 @@ import asyncio
13
14
  import threading
14
15
  import traceback
15
16
  import uuid
17
+ from collections import deque
18
+ from collections.abc import Iterable
16
19
  from pathlib import Path
17
- from typing import TYPE_CHECKING, Any, Iterable, Union
20
+ from typing import TYPE_CHECKING, Any, Union
18
21
 
19
22
  from pydantic import ValidationError
23
+ from typing_extensions import override
20
24
 
21
25
  from waldiez.models.waldiez import Waldiez
26
+ from waldiez.running.step_by_step.command_handler import CommandHandler
27
+ from waldiez.running.step_by_step.events_processor import EventProcessor
22
28
 
23
29
  from ..base_runner import WaldiezBaseRunner
30
+ from ..events_mixin import EventsMixin
24
31
  from ..exceptions import StopRunningException
25
- from ..run_results import WaldiezRunResults
32
+ from ..results_mixin import WaldiezRunResults
26
33
  from .breakpoints_mixin import BreakpointsMixin
27
34
  from .step_by_step_models import (
28
- HELP_MESSAGE,
29
35
  VALID_CONTROL_COMMANDS,
36
+ WaldiezDebugConfig,
30
37
  WaldiezDebugError,
31
38
  WaldiezDebugEventInfo,
32
39
  WaldiezDebugInputRequest,
@@ -37,13 +44,10 @@ from .step_by_step_models import (
37
44
  )
38
45
 
39
46
  if TYPE_CHECKING:
47
+ from autogen.agentchat import ConversableAgent # type: ignore
40
48
  from autogen.events import BaseEvent # type: ignore
41
49
  from autogen.messages import BaseMessage # type: ignore
42
50
 
43
-
44
- DEBUG_INPUT_PROMPT = (
45
- "[Step] (c)ontinue, (r)un, (q)uit, (i)nfo, (h)elp, (st)ats: "
46
- )
47
51
  MESSAGES = {
48
52
  "workflow_starting": "<Waldiez step-by-step> - Starting workflow...",
49
53
  "workflow_finished": "<Waldiez step-by-step> - Workflow finished",
@@ -52,12 +56,27 @@ MESSAGES = {
52
56
  "<Waldiez step-by-step> - Workflow execution failed: {error}"
53
57
  ),
54
58
  }
59
+ DEBUG_INPUT_PROMPT = (
60
+ # cspell: disable-next-line
61
+ "[Step] (c)ontinue, (r)un, (q)uit, (i)nfo, (h)elp, (st)ats: "
62
+ )
63
+
64
+
65
+ def gen_id() -> str:
66
+ """Generate a new id.
67
+
68
+ Returns
69
+ -------
70
+ str
71
+ The new id.
72
+ """
73
+ return str(uuid.uuid4())
55
74
 
56
75
 
57
76
  # pylint: disable=too-many-instance-attributes
58
77
  # noinspection DuplicatedCode,StrFormat
59
78
  class WaldiezStepByStepRunner(WaldiezBaseRunner, BreakpointsMixin):
60
- """Step-by-step runner with user interaction and debugging capabilities."""
79
+ """Refactored step-by-step runner with improved architecture."""
61
80
 
62
81
  def __init__(
63
82
  self,
@@ -67,7 +86,8 @@ class WaldiezStepByStepRunner(WaldiezBaseRunner, BreakpointsMixin):
67
86
  structured_io: bool = False,
68
87
  dot_env: str | Path | None = None,
69
88
  auto_continue: bool = False,
70
- breakpoints: Iterable[str] | None = None,
89
+ breakpoints: Iterable[Any] | None = None,
90
+ config: WaldiezDebugConfig | None = None,
71
91
  **kwargs: Any,
72
92
  ) -> None:
73
93
  """Initialize the step-by-step runner."""
@@ -79,24 +99,46 @@ class WaldiezStepByStepRunner(WaldiezBaseRunner, BreakpointsMixin):
79
99
  dot_env=dot_env,
80
100
  **kwargs,
81
101
  )
82
- BreakpointsMixin.__init__(self)
102
+ BreakpointsMixin.__init__(self, config=config)
103
+ self.set_agent_id_to_name(waldiez.flow.unique_names["agent_names"])
104
+
105
+ # Configuration
106
+ self._config = config or WaldiezDebugConfig()
107
+ self._config.auto_continue = auto_continue
108
+
109
+ # Core state
83
110
  self._event_count = 0
84
111
  self._processed_events = 0
85
- self._step_mode = True
86
- self._auto_continue = auto_continue
87
- # Initialize breakpoints from the mixin and set initial breakpoints
88
- if breakpoints:
89
- self.set_breakpoints(breakpoints)
90
- self._event_history: list[dict[str, Any]] = []
112
+ self._step_mode = self._config.step_mode
113
+
114
+ # Use deque for efficient FIFO operations on event history
115
+ self._event_history: deque[dict[str, Any]] = deque(
116
+ maxlen=self._config.max_event_history
117
+ )
91
118
  self._current_event: Union["BaseEvent", "BaseMessage", None] = None
119
+
120
+ # Participant tracking
92
121
  self._known_participants = self.waldiez.info.participants
93
122
  self._last_sender: str | None = None
94
123
  self._last_recipient: str | None = None
95
124
 
125
+ # Initialize breakpoints
126
+ if breakpoints:
127
+ _, errors = self.import_breakpoints(list(breakpoints))
128
+ if errors:
129
+ for error in errors:
130
+ self.log.warning("Breakpoint import error: %s", error)
131
+
132
+ # Command handling
133
+ self._command_handler = CommandHandler(self)
134
+ self._event_processor = EventProcessor(self)
135
+ auto_run = self.is_auto_run()
136
+ self._config.auto_continue = auto_run
137
+
96
138
  @property
97
139
  def auto_continue(self) -> bool:
98
140
  """Get whether auto-continue is enabled."""
99
- return self._auto_continue
141
+ return self._config.auto_continue
100
142
 
101
143
  @auto_continue.setter
102
144
  def auto_continue(self, value: bool) -> None:
@@ -107,321 +149,438 @@ class WaldiezStepByStepRunner(WaldiezBaseRunner, BreakpointsMixin):
107
149
  value : bool
108
150
  Whether to enable auto-continue.
109
151
  """
110
- self._auto_continue = value
152
+ self._config.auto_continue = value
153
+ self.log.debug("Auto-continue mode set to: %s", value)
111
154
 
112
155
  @property
113
- def stop_requested(self) -> threading.Event:
114
- """Get the stop requested event."""
115
- return self._stop_requested
156
+ def step_mode(self) -> bool:
157
+ """Get the step mode.
158
+
159
+ Returns
160
+ -------
161
+ bool
162
+ Whether the step mode is enabled.
163
+ """
164
+ return self._step_mode
165
+
166
+ @step_mode.setter
167
+ def step_mode(self, value: bool) -> None:
168
+ """Set the step mode.
169
+
170
+ Parameters
171
+ ----------
172
+ value : bool
173
+ Whether to enable step mode.
174
+ """
175
+ self._step_mode = value
176
+ self.log.debug("Step mode set to: %s", value)
177
+
178
+ @property
179
+ def last_sender(self) -> str | None:
180
+ """Get the last sender.
181
+
182
+ Returns
183
+ -------
184
+ str | None
185
+ The last sender, if available.
186
+ """
187
+ return self._last_sender
188
+
189
+ @last_sender.setter
190
+ def last_sender(self, value: str | None) -> None:
191
+ """Set the last sender.
192
+
193
+ Parameters
194
+ ----------
195
+ value : str | None
196
+ The last sender to set.
197
+ """
198
+ self._last_sender = value
199
+
200
+ @property
201
+ def last_recipient(self) -> str | None:
202
+ """Get the last recipient.
203
+
204
+ Returns
205
+ -------
206
+ str | None
207
+ The last recipient, if available.
208
+ """
209
+ return self._last_recipient
116
210
 
117
- def set_auto_continue(self, auto_continue: bool) -> None:
118
- """Set whether to automatically continue without user input.
211
+ @last_recipient.setter
212
+ def last_recipient(self, value: str | None) -> None:
213
+ """Set the last recipient.
119
214
 
120
215
  Parameters
121
216
  ----------
122
- auto_continue : bool
123
- Whether to automatically continue execution
124
- without waiting for user input.
217
+ value : str | None
218
+ The last recipient to set.
125
219
  """
126
- self._auto_continue = auto_continue
127
- self.log.info("Auto-continue mode set to: %s", auto_continue)
220
+ self._last_recipient = value
221
+
222
+ @property
223
+ def stop_requested(self) -> threading.Event:
224
+ """Get the stop requested event."""
225
+ return self._stop_requested
226
+
227
+ @property
228
+ def max_event_history(self) -> int:
229
+ """Get the maximum event history size."""
230
+ return self._config.max_event_history
128
231
 
129
- # pylint: disable=no-self-use
130
- # noinspection PyMethodMayBeStatic
131
- def print(self, *args: Any, **kwargs: Any) -> None:
132
- """Print.
232
+ def add_to_history(self, event_info: dict[str, Any]) -> None:
233
+ """Add an event to the history.
133
234
 
134
235
  Parameters
135
236
  ----------
136
- *args : Any
137
- Positional arguments to print.
138
- **kwargs : Any
139
- Keyword arguments to print.
237
+ event_info : dict[str, Any]
238
+ The event information to add to the history.
140
239
  """
141
- WaldiezBaseRunner._print(*args, **kwargs)
240
+ self._event_history.append(event_info)
241
+
242
+ def pop_event(self) -> None:
243
+ """Pop event from the history."""
244
+ if self._event_history:
245
+ self._event_history.popleft()
142
246
 
143
- def emit_event(self, event: Union["BaseEvent", "BaseMessage"]) -> None:
247
+ def emit_event(
248
+ self, event: Union["BaseEvent", "BaseMessage", dict[str, Any]]
249
+ ) -> None:
144
250
  """Emit an event.
145
251
 
146
252
  Parameters
147
253
  ----------
148
- event : Union[BaseEvent, BaseMessage]
254
+ event : BaseEvent | BaseMessage | dict[str, Any]
149
255
  The event to emit.
150
256
  """
151
- event_info = event.model_dump(
152
- mode="json", exclude_none=True, fallback=str
153
- )
154
- event_info["count"] = self._event_count
155
- self._last_sender = getattr(event, "sender", self._last_sender)
156
- self._last_recipient = getattr(event, "recipient", self._last_recipient)
157
- event_info["sender"] = self._last_sender
158
- event_info["recipient"] = self._last_recipient
257
+ if not isinstance(event, dict):
258
+ event_info = event.model_dump(
259
+ mode="json", exclude_none=True, fallback=str
260
+ )
261
+ event_info["count"] = self._event_count
262
+ event_info["sender"] = getattr(event, "sender", self._last_sender)
263
+ event_info["recipient"] = getattr(
264
+ event, "recipient", self._last_recipient
265
+ )
266
+ else:
267
+ event_info = event
159
268
  self.emit(WaldiezDebugEventInfo(event=event_info))
160
269
 
161
270
  # noinspection PyTypeHints
271
+ @override
162
272
  def emit(self, message: WaldiezDebugMessage) -> None:
163
273
  """Emit a debug message.
164
274
 
165
275
  Parameters
166
276
  ----------
167
277
  message : WaldiezDebugMessage
168
- The debug message to emit.
278
+ The message to emit.
169
279
  """
170
280
  message_dump = message.model_dump(
171
281
  mode="json", exclude_none=True, fallback=str
172
282
  )
173
283
  self.print(message_dump)
174
284
 
175
- def _show_event_info(self) -> None:
285
+ @property
286
+ def current_event(self) -> Union["BaseEvent", "BaseMessage", None]:
287
+ """Get the current event.
288
+
289
+ Returns
290
+ -------
291
+ Union["BaseEvent", "BaseMessage", None]
292
+ The current event, if available.
293
+ """
294
+ return self._current_event
295
+
296
+ @current_event.setter
297
+ def current_event(
298
+ self, value: Union["BaseEvent", "BaseMessage", None]
299
+ ) -> None:
300
+ """Set the current event.
301
+
302
+ Parameters
303
+ ----------
304
+ value : Union["BaseEvent", "BaseMessage", None]
305
+ The event to set as the current event.
306
+ """
307
+ self._current_event = value
308
+
309
+ @property
310
+ def event_count(self) -> int:
311
+ """Get the current event count.
312
+
313
+ Returns
314
+ -------
315
+ int
316
+ The current event count.
317
+ """
318
+ return self._event_count
319
+
320
+ def event_plus_one(self) -> None:
321
+ """Increment the current event count."""
322
+ self._event_count += 1
323
+
324
+ def show_event_info(self) -> None:
325
+ """Show detailed information about the current event."""
176
326
  if not self._current_event:
327
+ self.emit(WaldiezDebugError(error="No current event to display"))
177
328
  return
329
+
178
330
  event_info = self._current_event.model_dump(
179
331
  mode="json", exclude_none=True, fallback=str
180
332
  )
333
+ # Add additional context
334
+ event_info["_meta"] = {
335
+ "event_number": self._event_count,
336
+ "processed_events": self._processed_events,
337
+ "step_mode": self._step_mode,
338
+ "has_breakpoints": len(self._breakpoints) > 0,
339
+ }
181
340
  self.emit(WaldiezDebugEventInfo(event=event_info))
182
341
 
183
- def _show_stats(self) -> None:
342
+ def show_stats(self) -> None:
343
+ """Show comprehensive execution statistics."""
344
+ base_stats: dict[str, Any] = {
345
+ "execution": {
346
+ "events_processed": self._processed_events,
347
+ "total_events": self._event_count,
348
+ "processing_rate": (
349
+ f"{(self._processed_events / self._event_count * 100):.1f}%"
350
+ if self._event_count > 0
351
+ else "0%"
352
+ ),
353
+ },
354
+ "mode": {
355
+ "step_mode": self._step_mode,
356
+ "auto_continue": self._config.auto_continue,
357
+ },
358
+ "history": {
359
+ "event_history_count": len(self._event_history),
360
+ "max_history_size": self._config.max_event_history,
361
+ "memory_usage": f"{len(self._event_history) * 200}B (est.)",
362
+ },
363
+ "participants": {
364
+ "last_sender": self._last_sender,
365
+ "last_recipient": self._last_recipient,
366
+ "known_participants": len(self._known_participants),
367
+ },
368
+ }
369
+
370
+ # Merge with breakpoint stats
371
+ breakpoint_stats = self.get_breakpoint_stats()
184
372
  stats_dict: dict[str, Any] = {
185
- "events_processed": self._processed_events,
373
+ **base_stats,
374
+ "breakpoints": breakpoint_stats,
375
+ }
376
+
377
+ self.emit(WaldiezDebugStats(stats=stats_dict))
378
+
379
+ @property
380
+ def execution_stats(self) -> dict[str, Any]:
381
+ """Get comprehensive execution statistics.
382
+
383
+ Returns
384
+ -------
385
+ dict[str, Any]
386
+ A dictionary containing execution statistics.
387
+ """
388
+ base_stats: dict[str, Any] = {
186
389
  "total_events": self._event_count,
390
+ "processed_events": self._processed_events,
391
+ "event_processing_rate": (
392
+ self._processed_events / self._event_count
393
+ if self._event_count > 0
394
+ else 0
395
+ ),
187
396
  "step_mode": self._step_mode,
188
- "auto_continue": self._auto_continue,
189
- "breakpoints": sorted(self.get_breakpoints()),
397
+ "auto_continue": self._config.auto_continue,
190
398
  "event_history_count": len(self._event_history),
399
+ "last_sender": self._last_sender,
400
+ "last_recipient": self._last_recipient,
401
+ "known_participants": [
402
+ p.model_dump() for p in self._known_participants
403
+ ],
404
+ "config": self._config.model_dump(),
191
405
  }
192
- self.emit(WaldiezDebugStats(stats=stats_dict))
193
406
 
194
- def _get_user_response(
195
- self, user_response: str, request_id: str
196
- ) -> tuple[str | None, bool]:
197
- """Get user response for step-by-step execution.
407
+ return {**base_stats, "breakpoints": self.get_breakpoint_stats()}
198
408
 
199
- Parameters
200
- ----------
201
- user_response : str
202
- The user's response.
203
- request_id : str
204
- The ID of the input request.
409
+ @property
410
+ def event_history(self) -> list[dict[str, Any]]:
411
+ """Get the history of processed events.
205
412
 
206
413
  Returns
207
414
  -------
208
- tuple[str | None, bool]
209
- The user's response or None if invalid,
210
- and a boolean indicating validity.
415
+ list[dict[str, Any]]
416
+ A list of dictionaries containing event history.
211
417
  """
418
+ return list(self._event_history)
419
+
420
+ def reset_session(self) -> None:
421
+ """Reset the debugging session state."""
422
+ self._event_count = 0
423
+ self._processed_events = 0
424
+ self._event_history.clear()
425
+ self._current_event = None
426
+ self._last_sender = None
427
+ self._last_recipient = None
428
+ self.reset_stats()
429
+ self.log.info("Debug session reset")
430
+
431
+ def _get_user_response(
432
+ self,
433
+ user_response: str,
434
+ request_id: str,
435
+ skip_id_check: bool = False,
436
+ ) -> tuple[str | None, bool]:
437
+ """Get and validate user response."""
212
438
  try:
213
439
  response = WaldiezDebugInputResponse.model_validate_json(
214
440
  user_response
215
441
  )
216
442
  except ValidationError as exc:
443
+ # Handle raw CLI input
217
444
  got = user_response.strip().lower()
218
- # in cli mode, let's see if got raw response
219
- # instead of a structured one
220
445
  if got in VALID_CONTROL_COMMANDS:
221
446
  return got, True
222
447
  self.emit(WaldiezDebugError(error=f"Invalid input: {exc}"))
223
448
  return None, False
224
449
 
225
- if response.request_id != request_id:
450
+ if not skip_id_check and response.request_id != request_id:
226
451
  self.emit(
227
452
  WaldiezDebugError(
228
- error=(
229
- "Stale input received: "
230
- f"{response.request_id} != {request_id}"
231
- )
453
+ error=f"Stale input received: {response.request_id} != {request_id}"
232
454
  )
233
455
  )
234
456
  return None, False
457
+
235
458
  return response.data, True
236
459
 
237
- # pylint: disable=too-many-return-statements
238
- def _parse_user_action( # noqa: C901
460
+ def _parse_user_action(
239
461
  self, user_response: str, request_id: str
240
462
  ) -> WaldiezDebugStepAction:
241
- """Parse user action for step-by-step execution.
242
-
243
- Parameters
244
- ----------
245
- user_response : str
246
- The user's response.
247
- request_id : str
248
- The ID of the input request.
249
-
250
- Returns
251
- -------
252
- WaldiezDebugStepAction
253
- The action chosen by the user.
254
- """
463
+ """Parse user action using the command handler."""
255
464
  self.log.debug("Parsing user action... '%s'", user_response)
465
+
256
466
  user_input, is_valid = self._get_user_response(
257
- user_response, request_id=request_id
467
+ user_response,
468
+ request_id=request_id,
469
+ skip_id_check=True,
258
470
  )
259
471
  if not is_valid:
260
472
  return WaldiezDebugStepAction.UNKNOWN
261
- if not user_input:
262
- return WaldiezDebugStepAction.CONTINUE
263
- match user_input:
264
- case "c":
265
- self._step_mode = True
266
- return WaldiezDebugStepAction.CONTINUE
267
- case "s":
268
- self._step_mode = True
269
- return WaldiezDebugStepAction.STEP
270
- case "r":
271
- self._step_mode = False
272
- return WaldiezDebugStepAction.RUN
273
- case "q":
274
- self._stop_requested.set()
275
- return WaldiezDebugStepAction.QUIT
276
- case "i":
277
- self._show_event_info()
278
- return WaldiezDebugStepAction.INFO
279
- case "h":
280
- self.emit(HELP_MESSAGE)
281
- return WaldiezDebugStepAction.HELP
282
- case "st":
283
- self._show_stats()
284
- return WaldiezDebugStepAction.STATS
285
- case "ab":
286
- if self._current_event and hasattr(self._current_event, "type"):
287
- self.add_breakpoint(self._current_event.type)
288
- else:
289
- self.emit(
290
- WaldiezDebugError(
291
- error="No current event to add breakpoint for"
292
- )
293
- )
294
- return WaldiezDebugStepAction.ADD_BREAKPOINT
295
- case "rb":
296
- if self._current_event and hasattr(self._current_event, "type"):
297
- self.remove_breakpoint(self._current_event.type)
298
- else:
299
- self.emit(
300
- WaldiezDebugError(
301
- error="No current event to remove breakpoint for"
302
- )
303
- )
304
- return WaldiezDebugStepAction.REMOVE_BREAKPOINT
305
- case "lb":
306
- self.list_breakpoints()
307
- return WaldiezDebugStepAction.LIST_BREAKPOINTS
308
- case "cb":
309
- self.clear_breakpoints()
310
- return WaldiezDebugStepAction.CLEAR_BREAKPOINTS
311
- case _:
312
- self.emit(
313
- WaldiezDebugError(
314
- error=f"Unknown command: {user_input}, use 'h' for help"
315
- )
316
- )
317
- return WaldiezDebugStepAction.UNKNOWN
318
473
 
319
- def _get_user_action(self) -> WaldiezDebugStepAction:
320
- """Get user action for step-by-step execution.
474
+ return self._command_handler.handle_command(user_input or "")
321
475
 
322
- Returns
323
- -------
324
- WaldiezDebugStepAction
325
- The action chosen by the user.
326
- """
327
- if self._auto_continue:
328
- return WaldiezDebugStepAction.CONTINUE
476
+ def _get_user_action(self, force: bool) -> WaldiezDebugStepAction:
477
+ """Get user action with timeout support.
329
478
 
479
+ Parameters
480
+ ----------
481
+ force : bool
482
+ Force getting the user's action, even if in auto-run mode.
483
+ """
484
+ if self._config.auto_continue:
485
+ self.step_mode = True
486
+ if force:
487
+ self._config.auto_continue = False
488
+ else:
489
+ return WaldiezDebugStepAction.CONTINUE
330
490
  while True:
331
- # pylint: disable=too-many-try-statements
332
- request_id = str(uuid.uuid4())
491
+ request_id = gen_id()
333
492
  try:
334
- self.emit(
335
- WaldiezDebugInputRequest(
336
- prompt=DEBUG_INPUT_PROMPT, request_id=request_id
493
+ if not self.structured_io:
494
+ # if structured, we already do this (print the prompt)
495
+ self.emit(
496
+ WaldiezDebugInputRequest(
497
+ prompt=DEBUG_INPUT_PROMPT, request_id=request_id
498
+ )
337
499
  )
338
- )
339
- user_input = (
340
- WaldiezBaseRunner.get_user_input(DEBUG_INPUT_PROMPT)
341
- .strip()
342
- .lower()
343
- )
500
+ except Exception as e: # pylint: disable=broad-exception-caught
501
+ self.log.warning("Failed to emit input request: %s", e)
502
+ try:
503
+ user_input = EventsMixin.get_user_input(
504
+ DEBUG_INPUT_PROMPT,
505
+ request_id=request_id,
506
+ ).strip()
344
507
  return self._parse_user_action(
345
508
  user_input, request_id=request_id
346
509
  )
510
+
347
511
  except (KeyboardInterrupt, EOFError):
348
512
  self._stop_requested.set()
349
513
  return WaldiezDebugStepAction.QUIT
350
514
 
351
- # pylint: disable=too-many-return-statements
352
- async def _a_get_user_action(self) -> WaldiezDebugStepAction:
353
- """Get user action for step-by-step execution asynchronously.
354
-
355
- Returns
356
- -------
357
- WaldiezDebugStepAction
358
- The action chosen by the user.
359
- """
360
- if self._auto_continue:
361
- return WaldiezDebugStepAction.CONTINUE
515
+ async def _a_get_user_action(self, force: bool) -> WaldiezDebugStepAction:
516
+ """Get user action asynchronously."""
517
+ if self._config.auto_continue:
518
+ self.step_mode = True
519
+ if force:
520
+ self._config.auto_continue = False
521
+ else:
522
+ return WaldiezDebugStepAction.CONTINUE
362
523
 
363
524
  while True:
525
+ request_id = gen_id()
364
526
  # pylint: disable=too-many-try-statements
365
- request_id = str(uuid.uuid4())
366
527
  try:
367
528
  self.emit(
368
529
  WaldiezDebugInputRequest(
369
530
  prompt=DEBUG_INPUT_PROMPT, request_id=request_id
370
531
  )
371
532
  )
372
- user_input = await WaldiezBaseRunner.a_get_user_input(
533
+
534
+ user_input = await EventsMixin.a_get_user_input(
373
535
  DEBUG_INPUT_PROMPT
374
536
  )
375
- user_input = user_input.strip().lower()
537
+ user_input = user_input.strip()
376
538
  return self._parse_user_action(
377
539
  user_input, request_id=request_id
378
540
  )
541
+
379
542
  except (KeyboardInterrupt, EOFError):
380
543
  return WaldiezDebugStepAction.QUIT
381
544
 
382
- def _handle_step_interaction(self) -> bool:
545
+ def _handle_step_interaction(self, force: bool) -> bool:
383
546
  """Handle step-by-step user interaction.
384
547
 
385
- Returns
386
- -------
387
- bool
388
- True to continue execution, False to stop.
548
+ Parameters
549
+ ----------
550
+ force : bool
551
+ Force getting the user's action, even if in auto-run mode.
389
552
  """
390
- while True: # pragma: no branch
391
- action = self._get_user_action()
392
-
553
+ while True:
554
+ action = self._get_user_action(force)
393
555
  if action in (
394
556
  WaldiezDebugStepAction.CONTINUE,
395
557
  WaldiezDebugStepAction.STEP,
396
558
  ):
397
559
  return True
398
560
  if action == WaldiezDebugStepAction.RUN:
561
+ self._config.auto_continue = True
399
562
  return True
400
- if action == WaldiezDebugStepAction.QUIT: # pragma: no branch
563
+ if action == WaldiezDebugStepAction.QUIT:
401
564
  return False
565
+ # For other actions (info, help, etc.), continue the loop
402
566
 
403
- async def _a_handle_step_interaction(self) -> bool:
404
- """Handle step-by-step user interaction asynchronously.
405
-
406
- Returns
407
- -------
408
- bool
409
- True to continue execution, False to stop.
410
- """
411
- while True: # pragma: no branch
412
- action = await self._a_get_user_action()
413
-
567
+ async def _a_handle_step_interaction(self, force: bool) -> bool:
568
+ """Handle step-by-step user interaction asynchronously."""
569
+ while True:
570
+ action = await self._a_get_user_action(force)
414
571
  if action in (
415
572
  WaldiezDebugStepAction.CONTINUE,
416
573
  WaldiezDebugStepAction.STEP,
417
574
  ):
418
575
  return True
419
576
  if action == WaldiezDebugStepAction.RUN:
577
+ self._config.auto_continue = True
420
578
  return True
421
- if action == WaldiezDebugStepAction.QUIT: # pragma: no branch
579
+ if action == WaldiezDebugStepAction.QUIT:
422
580
  return False
581
+ # For other actions (info, help, etc.), continue the loop
423
582
 
424
- # pylint: disable=unused-argument
583
+ @override
425
584
  def _run(
426
585
  self,
427
586
  temp_dir: Path,
@@ -445,13 +604,13 @@ class WaldiezStepByStepRunner(WaldiezBaseRunner, BreakpointsMixin):
445
604
  # pylint: disable=too-many-try-statements,broad-exception-caught
446
605
  try:
447
606
  loaded_module = self._load_module(output_file, temp_dir)
448
- if self._stop_requested.is_set(): # pragma: no cover
607
+ if self._stop_requested.is_set():
449
608
  self.log.debug(
450
609
  "Step-by-step execution stopped before workflow start"
451
610
  )
452
611
  return []
453
612
 
454
- # noinspection DuplicatedCode
613
+ # Setup I/O
455
614
  if self.structured_io:
456
615
  stream = StructuredIOStream(
457
616
  uploads_root=uploads_root, is_async=False
@@ -459,9 +618,9 @@ class WaldiezStepByStepRunner(WaldiezBaseRunner, BreakpointsMixin):
459
618
  else:
460
619
  stream = IOStream.get_default()
461
620
 
462
- WaldiezBaseRunner._print = stream.print
463
- WaldiezBaseRunner._input = stream.input
464
- WaldiezBaseRunner._send = stream.send
621
+ EventsMixin.set_print_function(stream.print)
622
+ EventsMixin.set_input_function(stream.input)
623
+ EventsMixin.set_send_function(stream.send)
465
624
 
466
625
  self.print(MESSAGES["workflow_starting"])
467
626
  self.print(self.waldiez.info.model_dump_json())
@@ -475,77 +634,63 @@ class WaldiezStepByStepRunner(WaldiezBaseRunner, BreakpointsMixin):
475
634
  raise StopRunningException(StopRunningException.reason) from e
476
635
  results_container["exception"] = e
477
636
  traceback.print_exc()
478
- # noinspection StrFormat
479
637
  self.print(MESSAGES["workflow_failed"].format(error=str(e)))
480
638
  finally:
481
639
  results_container["completed"] = True
482
640
 
483
641
  return results_container["results"]
484
642
 
485
- def _on_event(self, event: Union["BaseEvent", "BaseMessage"]) -> bool:
486
- """Process an event with step-by-step debugging.
643
+ def _re_emit_if_needed(self, event_info: dict[str, Any]) -> None:
644
+ # emit again if type is text, swapping the sender and without recipient
645
+ if event_info.get("type", "") == "text":
646
+ event_info["sender"] = event_info["recipient"]
647
+ event_info["recipient"] = None
648
+ event_info["agents"]["sender"] = event_info["agents"]["recipient"]
649
+ event_info["agents"]["recipient"] = None
650
+ self.emit_event(event_info)
487
651
 
488
- Parameters
489
- ----------
490
- event : Union[BaseEvent, BaseMessage]
491
- The event to process.
492
-
493
- Returns
494
- -------
495
- bool
496
- True to continue, False to stop.
497
-
498
- Raises
499
- ------
500
- RuntimeError
501
- If an error occurs while processing the event.
502
- StopRunningException
503
- If execution is stopped by user request.
504
- """
505
- self._event_count += 1
506
- self._current_event = event
507
-
508
- if self._stop_requested.is_set():
509
- self.log.debug(
510
- "Step-by-step execution stopped before event processing"
511
- )
512
- return False
513
-
514
- # Store event in history
515
- event_info = event.model_dump(
516
- mode="json", exclude_none=True, fallback=str
517
- )
518
- event_info["count"] = self._event_count
519
- self._event_history.append(event_info)
520
-
521
- # pylint: disable=too-many-try-statements
652
+ def _on_event(
653
+ self,
654
+ event: Union["BaseEvent", "BaseMessage"],
655
+ agents: list["ConversableAgent"],
656
+ ) -> bool:
657
+ """Process an event with step-by-step debugging."""
658
+ # pylint: disable=too-many-try-statements,broad-exception-caught
522
659
  try:
523
- # Show event information if we should break
524
- if self.should_break_on_event(
525
- event, self._step_mode
526
- ): # pragma: no branch
527
- self.emit_event(event)
528
- if not self._handle_step_interaction():
660
+ # Use the event processor for core logic
661
+ result = self._event_processor.process_event(event, agents)
662
+
663
+ if result["action"] == "stop":
664
+ self.log.debug(
665
+ "Step-by-step execution stopped before event processing"
666
+ )
667
+ return False
668
+ event_info = result["event_info"]
669
+ self.emit_event(event_info)
670
+ # Handle breakpoint logic
671
+ if result["action"] == "break":
672
+ if not self._handle_step_interaction(force=True):
529
673
  self._stop_requested.set()
530
674
  if hasattr(event, "type") and event.type == "input_request":
531
675
  event.content.respond("exit")
532
676
  return True
533
677
  raise StopRunningException(StopRunningException.reason)
534
-
535
- # Process the event
536
- WaldiezBaseRunner.process_event(event)
678
+ self._re_emit_if_needed(event_info)
679
+ # Process the actual event
680
+ EventsMixin.process_event(event, agents, skip_send=True)
537
681
  self._processed_events += 1
538
682
 
539
683
  except Exception as e:
540
684
  if not isinstance(e, StopRunningException):
541
685
  raise RuntimeError(
542
- f"Error processing event {event}: "
543
- f"{e}\n{traceback.format_exc()}"
686
+ f"Error processing event {event}: {e}\n{traceback.format_exc()}"
544
687
  ) from e
545
688
  raise StopRunningException(StopRunningException.reason) from e
689
+
546
690
  return not self._stop_requested.is_set()
547
691
 
548
692
  # pylint: disable=too-complex
693
+ @override
549
694
  async def _a_run(
550
695
  self,
551
696
  temp_dir: Path,
@@ -558,19 +703,17 @@ class WaldiezStepByStepRunner(WaldiezBaseRunner, BreakpointsMixin):
558
703
  """Run the Waldiez workflow with step-by-step debugging (async)."""
559
704
 
560
705
  async def _execute_workflow() -> list[dict[str, Any]]:
561
- """Execute the workflow in an async context."""
562
706
  # pylint: disable=import-outside-toplevel
563
- from autogen.io import IOStream # pyright: ignore
707
+ from autogen.io import IOStream
564
708
 
565
709
  from waldiez.io import StructuredIOStream
566
710
 
567
- results: list[dict[str, Any]]
568
711
  # pylint: disable=too-many-try-statements,broad-exception-caught
569
712
  try:
570
713
  loaded_module = self._load_module(output_file, temp_dir)
571
- if self._stop_requested.is_set(): # pragma: no cover
714
+ if self._stop_requested.is_set():
572
715
  self.log.debug(
573
- "step-by-step execution stopped before workflow start"
716
+ "Step-by-step execution stopped before workflow start"
574
717
  )
575
718
  return []
576
719
 
@@ -581,15 +724,16 @@ class WaldiezStepByStepRunner(WaldiezBaseRunner, BreakpointsMixin):
581
724
  else:
582
725
  stream = IOStream.get_default()
583
726
 
584
- WaldiezBaseRunner._print = stream.print
585
- WaldiezBaseRunner._input = stream.input
586
- WaldiezBaseRunner._send = stream.send
727
+ EventsMixin.set_print_function(stream.print)
728
+ EventsMixin.set_input_function(stream.input)
729
+ EventsMixin.set_send_function(stream.send)
587
730
 
588
731
  self.print(MESSAGES["workflow_starting"])
589
732
  self.print(self.waldiez.info.model_dump_json())
590
733
 
591
734
  results = await loaded_module.main(on_event=self._a_on_event)
592
735
  self.print(MESSAGES["workflow_finished"])
736
+ return results
593
737
 
594
738
  except Exception as e:
595
739
  if StopRunningException.reason in str(e):
@@ -600,13 +744,9 @@ class WaldiezStepByStepRunner(WaldiezBaseRunner, BreakpointsMixin):
600
744
  traceback.print_exc()
601
745
  return []
602
746
 
603
- return results
604
-
605
- # Create cancellable task
747
+ # Create and monitor cancellable task
606
748
  task = asyncio.create_task(_execute_workflow())
607
-
608
- # Monitor for stop requests
609
- # pylint: disable=too-many-try-statements
749
+ # pylint: disable=too-many-try-statements,broad-exception-caught
610
750
  try:
611
751
  while not task.done():
612
752
  if self._stop_requested.is_set():
@@ -615,131 +755,46 @@ class WaldiezStepByStepRunner(WaldiezBaseRunner, BreakpointsMixin):
615
755
  break
616
756
  await asyncio.sleep(0.1)
617
757
  return await task
618
-
619
758
  except asyncio.CancelledError:
620
759
  self.log.debug("Step-by-step execution cancelled")
621
760
  return []
622
761
 
623
762
  async def _a_on_event(
624
- self, event: Union["BaseEvent", "BaseMessage"]
763
+ self,
764
+ event: Union["BaseEvent", "BaseMessage"],
765
+ agents: list["ConversableAgent"],
625
766
  ) -> bool:
626
- """Process an event with step-by-step debugging asynchronously.
627
-
628
- Parameters
629
- ----------
630
- event : Union[BaseEvent, BaseMessage]
631
- The event to process.
632
-
633
- Returns
634
- -------
635
- bool
636
- True to continue, False to stop.
637
-
638
- Raises
639
- ------
640
- RuntimeError
641
- If an error occurs while processing the event.
642
- StopRunningException
643
- If execution is stopped by user request.
644
- """
645
- self._event_count += 1
646
- self._current_event = event
647
-
648
- if self._stop_requested.is_set():
649
- self.log.debug(
650
- "Async step-by-step execution stopped before event processing"
651
- )
652
- return False
653
-
654
- # Store event in history
655
- event_info = event.model_dump(
656
- mode="json", exclude_none=True, fallback=str
657
- )
658
- event_info["count"] = self._event_count
659
- self._event_history.append(event_info)
660
-
661
- # pylint: disable=too-many-try-statements
767
+ """Process an event with step-by-step debugging asynchronously."""
768
+ # pylint: disable=too-many-try-statements,broad-exception-caught
662
769
  try:
663
- # Show event information if we should break
664
- if self.should_break_on_event(
665
- event, self._step_mode
666
- ): # pragma: no branch
667
- self.emit_event(event)
668
-
669
- # Handle step interaction
670
- if not await self._a_handle_step_interaction():
770
+ # Use the event processor for core logic
771
+ result = self._event_processor.process_event(event, agents)
772
+
773
+ if result["action"] == "stop":
774
+ self.log.debug(
775
+ "Async step-by-step execution stopped before event processing"
776
+ )
777
+ return False
778
+ event_info = result["event_info"]
779
+ self.emit_event(event_info)
780
+ # Handle breakpoint logic
781
+ if result["action"] == "break":
782
+ if not await self._a_handle_step_interaction(force=True):
671
783
  self._stop_requested.set()
672
784
  if hasattr(event, "type") and event.type == "input_request":
673
- event.content.respond("exit")
785
+ await event.content.respond("exit")
674
786
  return True
675
787
  raise StopRunningException(StopRunningException.reason)
676
-
677
- # Process the event
678
- await WaldiezBaseRunner.a_process_event(event)
788
+ self._re_emit_if_needed(event_info)
789
+ # Process the actual event
790
+ await EventsMixin.a_process_event(event, agents, skip_send=True)
679
791
  self._processed_events += 1
680
792
 
681
793
  except Exception as e:
682
794
  if not isinstance(e, StopRunningException):
683
795
  raise RuntimeError(
684
- f"Error processing event {event}: "
685
- f"{e}\n{traceback.format_exc()}"
796
+ f"Error processing event {event}: {e}\n{traceback.format_exc()}"
686
797
  ) from e
687
798
  raise StopRunningException(StopRunningException.reason) from e
688
- return not self._stop_requested.is_set()
689
799
 
690
- def get_execution_stats(self) -> dict[str, Any]:
691
- """Get execution statistics for step-by-step runner.
692
-
693
- Returns
694
- -------
695
- dict[str, Any]
696
- A dictionary containing execution statistics.
697
- """
698
- return {
699
- "total_events": self._event_count,
700
- "processed_events": self._processed_events,
701
- "event_processing_rate": (
702
- self._processed_events / self._event_count
703
- if self._event_count > 0
704
- else 0
705
- ),
706
- "step_mode": self._step_mode,
707
- "auto_continue": self._auto_continue,
708
- "breakpoints": sorted(self.get_breakpoints()),
709
- "event_history_count": len(self._event_history),
710
- "last_sender": self._last_sender,
711
- "last_recipient": self._last_recipient,
712
- "known_participants": [
713
- p.model_dump() for p in self._known_participants
714
- ],
715
- }
716
-
717
- def get_event_history(self) -> list[dict[str, Any]]:
718
- """Get the history of processed events.
719
-
720
- Returns
721
- -------
722
- list[dict[str, Any]]
723
- List of event information dictionaries.
724
- """
725
- return self._event_history.copy()
726
-
727
- def enable_auto_continue(self, enabled: bool = True) -> None:
728
- """Enable or disable auto-continue mode.
729
-
730
- Parameters
731
- ----------
732
- enabled : bool, optional
733
- Whether to enable auto-continue, by default True.
734
- """
735
- self._auto_continue = enabled
736
-
737
- def enable_step_mode(self, enabled: bool = True) -> None:
738
- """Enable or disable step mode.
739
-
740
- Parameters
741
- ----------
742
- enabled : bool, optional
743
- Whether to enable step mode, by default True.
744
- """
745
- self._step_mode = enabled
800
+ return not self._stop_requested.is_set()