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