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
@@ -3,9 +3,12 @@
3
3
  # pylint: disable=unused-argument
4
4
  """Breakpoints management mixin for step-by-step debugging."""
5
5
 
6
- from typing import TYPE_CHECKING, Any, Iterable, Union
6
+ import logging
7
+ from functools import lru_cache
8
+ from typing import TYPE_CHECKING, Any, Callable, Iterable, Union
7
9
 
8
10
  from .step_by_step_models import (
11
+ WaldiezBreakpoint,
9
12
  WaldiezDebugBreakpointAdded,
10
13
  WaldiezDebugBreakpointCleared,
11
14
  WaldiezDebugBreakpointRemoved,
@@ -19,13 +22,58 @@ if TYPE_CHECKING:
19
22
  from autogen.messages import BaseMessage # type: ignore
20
23
 
21
24
 
25
+ def handle_breakpoint_errors(func: Callable[..., bool]) -> Callable[..., bool]:
26
+ """Handle breakpoint-related errors.
27
+
28
+ Parameters
29
+ ----------
30
+ func : Callable
31
+ The function to decorate.
32
+
33
+ Returns
34
+ -------
35
+ Callable
36
+ The decorated function.
37
+ """
38
+
39
+ def _wrapper(self: "BreakpointsMixin", *args: Any, **kwargs: Any) -> bool:
40
+ try:
41
+ return func(self, *args, **kwargs)
42
+ except ValueError as e:
43
+ self.emit(WaldiezDebugError(error=f"Breakpoint error: {e}"))
44
+ return False
45
+ except Exception as e: # pylint: disable=broad-exception-caught
46
+ self.emit(
47
+ WaldiezDebugError(
48
+ error=f"Unexpected error in {func.__name__}: {e}"
49
+ )
50
+ )
51
+ logging.exception("Error in %s", func.__name__)
52
+ return False
53
+
54
+ return _wrapper
55
+
56
+
22
57
  class BreakpointsMixin:
23
58
  """Mixin class for managing breakpoints in step-by-step debugging."""
24
59
 
25
60
  def __init__(self, *args: Any, **kwargs: Any) -> None:
26
61
  """Initialize breakpoints storage."""
27
- self._breakpoints: set[str] = set()
62
+ self._breakpoints: set[WaldiezBreakpoint] = set()
28
63
 
64
+ # Statistics for monitoring
65
+ self._breakpoint_stats = {
66
+ "total_matches": 0,
67
+ "cache_hits": 0,
68
+ "cache_misses": 0,
69
+ }
70
+
71
+ # Create the cached function with proper binding
72
+ self._check_breakpoint_match_cached = lru_cache(maxsize=1000)(
73
+ self._check_breakpoint_match_impl
74
+ )
75
+
76
+ # noinspection PyTypeHints
29
77
  def emit(self, message: WaldiezDebugMessage) -> None:
30
78
  """Emit a debug message. Implemented by the class using this mixin.
31
79
 
@@ -36,31 +84,109 @@ class BreakpointsMixin:
36
84
  """
37
85
  raise NotImplementedError("emit method must be implemented")
38
86
 
39
- def add_breakpoint(self, event_type: str) -> None:
40
- """Add a breakpoint for an event type.
87
+ def _invalidate_cache(self) -> None:
88
+ """Invalidate the event matching cache when breakpoints change."""
89
+ self._check_breakpoint_match_cached.cache_clear()
90
+
91
+ def _get_breakpoints_signature(self) -> frozenset[str]:
92
+ """Get a hashable signature of current breakpoints."""
93
+ return frozenset(str(bp) for bp in self._breakpoints)
94
+
95
+ def _check_breakpoint_match_impl(
96
+ self,
97
+ event_type: str,
98
+ sender: str,
99
+ recipient: str,
100
+ breakpoints_sig: frozenset[str],
101
+ ) -> bool:
102
+ """Check if the event matches any breakpoints.
41
103
 
42
104
  Parameters
43
105
  ----------
44
106
  event_type : str
45
- The event type to add a breakpoint for.
107
+ The event type to check.
108
+ sender : str
109
+ The event sender.
110
+ recipient : str
111
+ The event recipient.
112
+ breakpoints_sig : frozenset[str]
113
+ Signature of current breakpoints for cache invalidation.
114
+
115
+ Returns
116
+ -------
117
+ bool
118
+ True if any breakpoint matches, False otherwise.
119
+ """
120
+ event_dict = {
121
+ "type": event_type,
122
+ "sender": sender,
123
+ "recipient": recipient,
124
+ }
125
+
126
+ # Reconstruct breakpoints from signature for cache safety
127
+ # noinspection PyBroadException
128
+ try:
129
+ breakpoints = {
130
+ WaldiezBreakpoint.from_string(bp_str)
131
+ for bp_str in breakpoints_sig
132
+ }
133
+ return any(bp.matches(event_dict) for bp in breakpoints)
134
+ except Exception: # pylint: disable=broad-exception-caught
135
+ # Fallback to current breakpoints if signature is malformed
136
+ return any(bp.matches(event_dict) for bp in self._breakpoints)
137
+
138
+ @handle_breakpoint_errors
139
+ def add_breakpoint(self, spec: str) -> bool:
140
+ """Add a breakpoint for an event type.
141
+
142
+ Parameters
143
+ ----------
144
+ spec : str
145
+ The event type specification to add a breakpoint for.
146
+
147
+ Returns
148
+ -------
149
+ bool
150
+ True if the breakpoint was added successfully, False otherwise.
46
151
  """
47
- if not event_type or not isinstance(event_type, str): # pyright: ignore
152
+ if not spec or not isinstance(spec, str): # pyright: ignore
48
153
  self.emit(
49
154
  WaldiezDebugError(
50
155
  error="Invalid event type: must be a non-empty string"
51
156
  )
52
157
  )
53
- return
158
+ return False
159
+ # pylint: disable=too-many-try-statements
160
+ try:
161
+ breakpoint_obj = WaldiezBreakpoint.from_string(spec)
162
+
163
+ # Check if breakpoint already exists
164
+ if breakpoint_obj in self._breakpoints:
165
+ self.emit(
166
+ WaldiezDebugError(
167
+ error=f"Breakpoint for '{spec}' already exists"
168
+ )
169
+ )
170
+ return False
54
171
 
55
- self._breakpoints.add(event_type)
56
- self.emit(WaldiezDebugBreakpointAdded(breakpoint=event_type))
172
+ self._breakpoints.add(breakpoint_obj)
173
+ self._invalidate_cache()
174
+ self.emit(WaldiezDebugBreakpointAdded(breakpoint=spec))
175
+ return True
57
176
 
58
- def remove_breakpoint(self, event_type: str) -> bool:
59
- """Remove a breakpoint for an event type.
177
+ except ValueError as e:
178
+ self.emit(
179
+ WaldiezDebugError(error=f"Invalid breakpoint format: {e}")
180
+ )
181
+ return False
182
+
183
+ @handle_breakpoint_errors
184
+ def remove_breakpoint(self, spec: str | WaldiezBreakpoint) -> bool:
185
+ """Remove a breakpoint based on its specification.
60
186
 
61
187
  Parameters
62
188
  ----------
63
- event_type : str
189
+ spec : str | WaldiezBreakpoint
64
190
  The event type to remove the breakpoint for.
65
191
 
66
192
  Returns
@@ -68,76 +194,131 @@ class BreakpointsMixin:
68
194
  bool
69
195
  True if the breakpoint was removed, False if it didn't exist.
70
196
  """
71
- if not event_type or not isinstance(event_type, str): # pyright: ignore
72
- self.emit(
73
- WaldiezDebugError(
74
- error="Invalid event type: must be a non-empty string"
197
+ if isinstance(spec, WaldiezBreakpoint):
198
+ breakpoint_obj = spec
199
+ spec_str = str(spec)
200
+ elif isinstance(spec, str) and spec: # pyright: ignore
201
+ try:
202
+ breakpoint_obj = WaldiezBreakpoint.from_string(spec)
203
+ spec_str = spec
204
+ except ValueError as e:
205
+ self.emit(
206
+ WaldiezDebugError(error=f"Invalid breakpoint format: {e}")
75
207
  )
208
+ return False
209
+ else:
210
+ self.emit(
211
+ WaldiezDebugError(error="Invalid breakpoint specification")
76
212
  )
77
213
  return False
78
214
 
79
- if event_type in self._breakpoints:
80
- self._breakpoints.remove(event_type)
81
- self.emit(WaldiezDebugBreakpointRemoved(breakpoint=event_type))
215
+ if breakpoint_obj in self._breakpoints:
216
+ self._breakpoints.remove(breakpoint_obj)
217
+ self._invalidate_cache()
218
+ self.emit(WaldiezDebugBreakpointRemoved(breakpoint=spec_str))
82
219
  return True
220
+
83
221
  self.emit(
84
222
  WaldiezDebugError(
85
- error=f"Breakpoint for '{event_type}' does not exist"
223
+ error=f"Breakpoint for '{spec_str}' does not exist"
86
224
  )
87
225
  )
88
226
  return False
89
227
 
90
228
  def list_breakpoints(self) -> None:
91
229
  """List all current breakpoints."""
92
- self.emit(
93
- WaldiezDebugBreakpointsList(breakpoints=sorted(self._breakpoints))
94
- )
230
+ breakpoints_list = sorted(self._breakpoints, key=str)
231
+ self.emit(WaldiezDebugBreakpointsList(breakpoints=breakpoints_list))
95
232
 
96
233
  def clear_breakpoints(self) -> None:
97
234
  """Clear all breakpoints."""
98
235
  count = len(self._breakpoints)
99
236
  self._breakpoints.clear()
237
+ self._invalidate_cache()
238
+
100
239
  if count > 0:
101
240
  self.emit(
102
241
  WaldiezDebugBreakpointCleared(
103
242
  message=f"Cleared {count} breakpoint(s)"
104
243
  )
105
244
  )
245
+ else:
246
+ self.emit(
247
+ WaldiezDebugBreakpointCleared(message="No breakpoints to clear")
248
+ )
106
249
 
107
- def set_breakpoints(self, event_types: Iterable[str]) -> None:
108
- """Set which event types to break on.
250
+ @handle_breakpoint_errors
251
+ def set_breakpoints(self, specs: Iterable[str | WaldiezBreakpoint]) -> bool:
252
+ """Set which breakpoints to activate.
109
253
 
110
254
  Parameters
111
255
  ----------
112
- event_types : Iterable[str]
113
- Iterable of event types to break on. Empty means break on all.
256
+ specs : Iterable[str | WaldiezBreakpoint]
257
+ Iterable of event types to break on. Empty means no breakpoints.
258
+
259
+ Returns
260
+ -------
261
+ bool
262
+ True if all breakpoints were set successfully, False if any failed.
114
263
  """
115
- self._breakpoints = set(event_types)
264
+ new_breakpoints: set[WaldiezBreakpoint] = set()
265
+ errors: list[str] = []
266
+
267
+ for spec in specs:
268
+ try:
269
+ if isinstance(spec, WaldiezBreakpoint):
270
+ new_breakpoints.add(spec)
271
+ else:
272
+ new_breakpoints.add(WaldiezBreakpoint.from_string(spec))
273
+ except ValueError as e:
274
+ errors.append(f"Invalid breakpoint '{spec}': {e}")
275
+
276
+ if errors:
277
+ for error in errors:
278
+ self.emit(WaldiezDebugError(error=error))
279
+ return False
116
280
 
117
- def get_breakpoints(self) -> set[str]:
281
+ old_count = len(self._breakpoints)
282
+ self._breakpoints = new_breakpoints
283
+ new_count = len(self._breakpoints)
284
+ self._invalidate_cache()
285
+
286
+ self.emit(
287
+ WaldiezDebugBreakpointCleared(
288
+ message=f"Updated breakpoints: {old_count} -> {new_count}"
289
+ )
290
+ )
291
+ return True
292
+
293
+ def get_breakpoints(self) -> set[WaldiezBreakpoint]:
118
294
  """Get current breakpoints.
119
295
 
120
296
  Returns
121
297
  -------
122
- set[str]
298
+ set[WaldiezBreakpoint]
123
299
  Set of current breakpoint event types.
124
300
  """
125
301
  return self._breakpoints.copy()
126
302
 
127
- def has_breakpoint(self, event_type: str) -> bool:
128
- """Check if a breakpoint exists for an event type.
303
+ def has_breakpoint(self, spec: str | WaldiezBreakpoint) -> bool:
304
+ """Check if a breakpoint exists.
129
305
 
130
306
  Parameters
131
307
  ----------
132
- event_type : str
133
- The event type to check.
308
+ spec : str | WaldiezBreakpoint
309
+ The breakpoint specification to check.
134
310
 
135
311
  Returns
136
312
  -------
137
313
  bool
138
314
  True if a breakpoint exists for this event type.
139
315
  """
140
- return event_type in self._breakpoints
316
+ try:
317
+ if isinstance(spec, WaldiezBreakpoint):
318
+ return spec in self._breakpoints
319
+ return WaldiezBreakpoint.from_string(spec) in self._breakpoints
320
+ except ValueError:
321
+ return False
141
322
 
142
323
  def should_break_on_event(
143
324
  self, event: Union["BaseEvent", "BaseMessage"], step_mode: bool = True
@@ -156,9 +337,6 @@ class BreakpointsMixin:
156
337
  bool
157
338
  True if we should break, False otherwise.
158
339
  """
159
- if not step_mode:
160
- return False
161
-
162
340
  # Get event type
163
341
  event_type = getattr(event, "type", "unknown")
164
342
 
@@ -166,23 +344,169 @@ class BreakpointsMixin:
166
344
  if event_type == "input_request":
167
345
  return False
168
346
 
169
- # If no specific breakpoints set, break on all events
170
- if not self._breakpoints:
347
+ # Quick path: if no breakpoints and not in step mode, don't break
348
+ if not self._breakpoints and not step_mode:
349
+ return False
350
+
351
+ # Quick path: if step mode and no specific breakpoints,
352
+ # break on everything
353
+ if step_mode and not self._breakpoints:
171
354
  return True
172
355
 
173
- # Check if this event type has a breakpoint
174
- return event_type in self._breakpoints
356
+ # Check if this event matches any breakpoint using caching
357
+ if hasattr(event, "model_dump"):
358
+ # pylint: disable=too-many-try-statements,broad-exception-caught
359
+ try:
360
+ event_dict = event.model_dump(
361
+ mode="python", exclude_none=True, fallback=str
362
+ )
363
+
364
+ # Extract event details for cache key
365
+ event_type_key = event_dict.get("type", "unknown")
366
+ sender = event_dict.get("sender", "")
367
+ recipient = event_dict.get("recipient", "")
368
+
369
+ # Get current breakpoints signature for cache invalidation
370
+ breakpoints_sig = self._get_breakpoints_signature()
371
+
372
+ # Check cached result
373
+ # noinspection PyBroadException
374
+ try:
375
+ matches_breakpoint = self._check_breakpoint_match_cached(
376
+ event_type_key, sender, recipient, breakpoints_sig
377
+ )
378
+ self._breakpoint_stats["cache_hits"] += 1
379
+ except Exception:
380
+ # Fallback to non-cached check
381
+ matches_breakpoint = any(
382
+ bp.matches(event_dict) for bp in self._breakpoints
383
+ )
384
+ self._breakpoint_stats["cache_misses"] += 1
385
+
386
+ self._breakpoint_stats["total_matches"] += 1
387
+
388
+ # If any breakpoint matches: break regardless of step_mode
389
+ if matches_breakpoint:
390
+ return True
391
+
392
+ except Exception as e: # pylint: disable=broad-exception-caught
393
+ logging.warning("Error processing event for breakpoints: %s", e)
394
+ self._breakpoint_stats["cache_misses"] += 1
395
+
396
+ # No specific breakpoints matched:
397
+ # - If step_mode, break on every event (single-step behavior)
398
+ # - If not step_mode, do not break
399
+ return bool(step_mode)
175
400
 
176
401
  def get_breakpoint_stats(self) -> dict[str, Any]:
177
- """Get breakpoint statistics.
402
+ """Get breakpoint statistics including performance metrics.
178
403
 
179
404
  Returns
180
405
  -------
181
406
  dict[str, Any]
182
407
  Dictionary containing breakpoint statistics.
183
408
  """
409
+ breakpoints: list[dict[str, Any]] = [
410
+ {
411
+ "type": bp.type.value,
412
+ "event_type": bp.event_type,
413
+ "agent_name": bp.agent_name,
414
+ "description": bp.description,
415
+ "string_repr": str(bp),
416
+ }
417
+ for bp in self._breakpoints
418
+ ]
419
+
420
+ # Calculate cache efficiency
421
+ total_checks = (
422
+ self._breakpoint_stats["cache_hits"]
423
+ + self._breakpoint_stats["cache_misses"]
424
+ )
425
+ cache_hit_rate = (
426
+ self._breakpoint_stats["cache_hits"] / total_checks
427
+ if total_checks > 0
428
+ else 0
429
+ )
430
+
431
+ # Get cache info from lru_cache
432
+ cache_info = self._check_breakpoint_match_cached.cache_info()
433
+
184
434
  return {
185
435
  "total_breakpoints": len(self._breakpoints),
186
- "breakpoints": sorted(self._breakpoints),
436
+ "breakpoints": breakpoints,
187
437
  "has_breakpoints": len(self._breakpoints) > 0,
438
+ "cache_stats": {
439
+ "cache_hit_rate": f"{cache_hit_rate:.2%}",
440
+ "cache_size": cache_info.currsize,
441
+ "cache_maxsize": cache_info.maxsize,
442
+ "total_matches": self._breakpoint_stats["total_matches"],
443
+ "cache_hits": cache_info.hits,
444
+ "cache_misses": cache_info.misses,
445
+ },
446
+ "performance": {
447
+ "lru_cache_info": {
448
+ "hits": cache_info.hits,
449
+ "misses": cache_info.misses,
450
+ "maxsize": cache_info.maxsize,
451
+ "currsize": cache_info.currsize,
452
+ }
453
+ },
454
+ }
455
+
456
+ def reset_stats(self) -> None:
457
+ """Reset breakpoint statistics."""
458
+ self._breakpoint_stats = {
459
+ "total_matches": 0,
460
+ "cache_hits": 0,
461
+ "cache_misses": 0,
188
462
  }
463
+ self._invalidate_cache()
464
+
465
+ def optimize_cache(self) -> None:
466
+ """Manually optimize the cache by clearing it."""
467
+ self._invalidate_cache()
468
+
469
+ def export_breakpoints(self) -> list[str]:
470
+ """Export breakpoints as a list of strings for persistence.
471
+
472
+ Returns
473
+ -------
474
+ list[str]
475
+ List of breakpoint specifications as strings.
476
+ """
477
+ return [str(bp) for bp in sorted(self._breakpoints, key=str)]
478
+
479
+ def import_breakpoints(
480
+ self, breakpoint_specs: list[str]
481
+ ) -> tuple[int, list[str]]:
482
+ """Import breakpoints from a list of string specifications.
483
+
484
+ Parameters
485
+ ----------
486
+ breakpoint_specs : list[str]
487
+ List of breakpoint specifications as strings.
488
+
489
+ Returns
490
+ -------
491
+ tuple[int, list[str]]
492
+ Tuple of (successful_imports, error_messages).
493
+ """
494
+ successful = 0
495
+ errors: list[str] = []
496
+
497
+ for spec in breakpoint_specs:
498
+ # pylint: disable=too-many-try-statements
499
+ try:
500
+ breakpoint_obj = WaldiezBreakpoint.from_string(spec)
501
+ if breakpoint_obj not in self._breakpoints:
502
+ self._breakpoints.add(breakpoint_obj)
503
+ successful += 1
504
+ else:
505
+ errors.append(f"Breakpoint '{spec}' already exists")
506
+ except ValueError as e:
507
+ errors.append(f"Invalid breakpoint '{spec}': {e}")
508
+
509
+ if successful > 0:
510
+ self._invalidate_cache()
511
+
512
+ return successful, errors
@@ -0,0 +1,151 @@
1
+ # SPDX-License-Identifier: Apache-2.0.
2
+ # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
+ # pylint: disable=unused-argument
4
+ """Command handler for step-by-step execution."""
5
+
6
+ from typing import TYPE_CHECKING, Callable
7
+
8
+ from .step_by_step_models import (
9
+ HELP_MESSAGE,
10
+ WaldiezDebugError,
11
+ WaldiezDebugStepAction,
12
+ )
13
+
14
+ if TYPE_CHECKING:
15
+ # noinspection PyUnusedImports
16
+ from .step_by_step_runner import WaldiezStepByStepRunner
17
+
18
+
19
+ # pylint: disable=too-few-public-methods
20
+ class CommandHandler:
21
+ """Handler for debug commands to reduce complexity in main runner."""
22
+
23
+ def __init__(self, runner: "WaldiezStepByStepRunner"):
24
+ self.runner = runner
25
+ self._command_map: dict[
26
+ str, Callable[[str | None], WaldiezDebugStepAction]
27
+ ] = {
28
+ "c": self._handle_continue,
29
+ "s": self._handle_step,
30
+ "r": self._handle_run,
31
+ "q": self._handle_quit,
32
+ "i": self._handle_info,
33
+ "h": self._handle_help,
34
+ "st": self._handle_stats,
35
+ "ab": self._handle_add_breakpoint,
36
+ "rb": self._handle_remove_breakpoint,
37
+ "lb": self._handle_list_breakpoints,
38
+ "cb": self._handle_clear_breakpoints,
39
+ }
40
+
41
+ def handle_command(self, command_line: str) -> WaldiezDebugStepAction:
42
+ """Handle a command line input.
43
+
44
+ Parameters
45
+ ----------
46
+ command_line : str
47
+ The command line input to handle.
48
+
49
+ Returns
50
+ -------
51
+ WaldiezDebugStepAction
52
+ The action to take for the command.
53
+ """
54
+ if not command_line or not command_line.strip():
55
+ return self._handle_step(None) # Enter = step
56
+ parts = command_line.strip().split(maxsplit=1)
57
+ if not parts or not parts[0]:
58
+ return self._handle_unknown(None)
59
+ command = parts[0].lower()
60
+ args = parts[1] if len(parts) > 1 else None
61
+
62
+ handler = self._command_map.get(command, self._handle_unknown)
63
+ return handler(args)
64
+
65
+ def _handle_continue(self, args: str | None) -> WaldiezDebugStepAction:
66
+ self.runner.step_mode = True
67
+ return WaldiezDebugStepAction.CONTINUE
68
+
69
+ def _handle_step(self, args: str | None) -> WaldiezDebugStepAction:
70
+ self.runner.step_mode = True
71
+ return WaldiezDebugStepAction.STEP
72
+
73
+ def _handle_run(self, args: str | None) -> WaldiezDebugStepAction:
74
+ self.runner.step_mode = False
75
+ return WaldiezDebugStepAction.RUN
76
+
77
+ def _handle_quit(self, args: str | None) -> WaldiezDebugStepAction:
78
+ self.runner.set_stop_requested()
79
+ return WaldiezDebugStepAction.QUIT
80
+
81
+ def _handle_info(self, args: str | None) -> WaldiezDebugStepAction:
82
+ self.runner.show_event_info()
83
+ return WaldiezDebugStepAction.INFO
84
+
85
+ def _handle_help(self, args: str | None) -> WaldiezDebugStepAction:
86
+ self.runner.emit(HELP_MESSAGE)
87
+ return WaldiezDebugStepAction.HELP
88
+
89
+ def _handle_stats(self, args: str | None) -> WaldiezDebugStepAction:
90
+ self.runner.show_stats()
91
+ return WaldiezDebugStepAction.STATS
92
+
93
+ def _handle_add_breakpoint(
94
+ self, args: str | None
95
+ ) -> WaldiezDebugStepAction:
96
+ if args:
97
+ self.runner.add_breakpoint(args)
98
+ return WaldiezDebugStepAction.ADD_BREAKPOINT
99
+ current_event = self.runner.current_event
100
+ if current_event and hasattr(current_event, "type"):
101
+ self.runner.add_breakpoint(getattr(current_event, "type", ""))
102
+ return WaldiezDebugStepAction.ADD_BREAKPOINT
103
+ # else:
104
+ self.runner.emit(
105
+ WaldiezDebugError(
106
+ error=(
107
+ "No breakpoint specification provided "
108
+ "and no current event available"
109
+ )
110
+ )
111
+ )
112
+ return WaldiezDebugStepAction.ADD_BREAKPOINT
113
+
114
+ def _handle_remove_breakpoint(
115
+ self, args: str | None
116
+ ) -> WaldiezDebugStepAction:
117
+ if args:
118
+ self.runner.remove_breakpoint(args)
119
+ return WaldiezDebugStepAction.REMOVE_BREAKPOINT
120
+ current_event = self.runner.current_event
121
+ if current_event and hasattr(current_event, "type"):
122
+ self.runner.remove_breakpoint(getattr(current_event, "type", ""))
123
+ return WaldiezDebugStepAction.REMOVE_BREAKPOINT
124
+ # else:
125
+ self.runner.emit(
126
+ WaldiezDebugError(
127
+ error=(
128
+ "No breakpoint specification provided and "
129
+ "no current event available"
130
+ )
131
+ )
132
+ )
133
+ return WaldiezDebugStepAction.REMOVE_BREAKPOINT
134
+
135
+ def _handle_list_breakpoints(
136
+ self, args: str | None
137
+ ) -> WaldiezDebugStepAction:
138
+ self.runner.list_breakpoints()
139
+ return WaldiezDebugStepAction.LIST_BREAKPOINTS
140
+
141
+ def _handle_clear_breakpoints(
142
+ self, args: str | None
143
+ ) -> WaldiezDebugStepAction:
144
+ self.runner.clear_breakpoints()
145
+ return WaldiezDebugStepAction.CLEAR_BREAKPOINTS
146
+
147
+ def _handle_unknown(self, args: str | None) -> WaldiezDebugStepAction:
148
+ self.runner.emit(
149
+ WaldiezDebugError(error="Unknown command. Use 'h' for help")
150
+ )
151
+ return WaldiezDebugStepAction.UNKNOWN