waldiez 0.6.0__py3-none-any.whl → 0.6.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (188) hide show
  1. waldiez/__init__.py +1 -1
  2. waldiez/_version.py +1 -1
  3. waldiez/cli.py +18 -7
  4. waldiez/cli_extras/jupyter.py +3 -0
  5. waldiez/cli_extras/runner.py +3 -1
  6. waldiez/cli_extras/studio.py +3 -1
  7. waldiez/exporter.py +9 -3
  8. waldiez/exporting/agent/exporter.py +9 -10
  9. waldiez/exporting/agent/extras/captain_agent_extras.py +6 -6
  10. waldiez/exporting/agent/extras/doc_agent_extras.py +6 -6
  11. waldiez/exporting/agent/extras/group_manager_agent_extas.py +34 -23
  12. waldiez/exporting/agent/extras/group_member_extras.py +6 -5
  13. waldiez/exporting/agent/extras/handoffs/after_work.py +1 -1
  14. waldiez/exporting/agent/extras/handoffs/available.py +1 -1
  15. waldiez/exporting/agent/extras/handoffs/condition.py +3 -2
  16. waldiez/exporting/agent/extras/handoffs/handoff.py +1 -1
  17. waldiez/exporting/agent/extras/handoffs/target.py +6 -4
  18. waldiez/exporting/agent/extras/rag/chroma_extras.py +27 -19
  19. waldiez/exporting/agent/extras/rag/mongo_extras.py +8 -8
  20. waldiez/exporting/agent/extras/rag/pgvector_extras.py +5 -5
  21. waldiez/exporting/agent/extras/rag/qdrant_extras.py +5 -4
  22. waldiez/exporting/agent/extras/rag/vector_db_extras.py +1 -1
  23. waldiez/exporting/agent/extras/rag_user_proxy_agent_extras.py +5 -7
  24. waldiez/exporting/agent/extras/reasoning_agent_extras.py +3 -5
  25. waldiez/exporting/chats/exporter.py +4 -4
  26. waldiez/exporting/chats/processor.py +1 -2
  27. waldiez/exporting/chats/utils/common.py +89 -48
  28. waldiez/exporting/chats/utils/group.py +9 -9
  29. waldiez/exporting/chats/utils/nested.py +7 -7
  30. waldiez/exporting/chats/utils/sequential.py +1 -1
  31. waldiez/exporting/chats/utils/single.py +2 -2
  32. waldiez/exporting/core/content.py +7 -7
  33. waldiez/exporting/core/context.py +5 -3
  34. waldiez/exporting/core/exporter.py +5 -3
  35. waldiez/exporting/core/exporters.py +2 -2
  36. waldiez/exporting/core/extras/agent_extras/captain_extras.py +2 -2
  37. waldiez/exporting/core/extras/agent_extras/group_manager_extras.py +2 -2
  38. waldiez/exporting/core/extras/agent_extras/rag_user_extras.py +2 -2
  39. waldiez/exporting/core/extras/agent_extras/standard_extras.py +3 -8
  40. waldiez/exporting/core/extras/base.py +7 -5
  41. waldiez/exporting/core/extras/flow_extras.py +4 -5
  42. waldiez/exporting/core/extras/model_extras.py +2 -2
  43. waldiez/exporting/core/extras/path_resolver.py +1 -2
  44. waldiez/exporting/core/extras/serializer.py +2 -2
  45. waldiez/exporting/core/protocols.py +6 -5
  46. waldiez/exporting/core/result.py +25 -28
  47. waldiez/exporting/core/types.py +10 -10
  48. waldiez/exporting/core/utils/llm_config.py +2 -2
  49. waldiez/exporting/core/validation.py +10 -11
  50. waldiez/exporting/flow/execution_generator.py +98 -10
  51. waldiez/exporting/flow/exporter.py +2 -2
  52. waldiez/exporting/flow/factory.py +2 -2
  53. waldiez/exporting/flow/file_generator.py +4 -2
  54. waldiez/exporting/flow/merger.py +5 -3
  55. waldiez/exporting/flow/orchestrator.py +72 -2
  56. waldiez/exporting/flow/utils/common.py +5 -5
  57. waldiez/exporting/flow/utils/importing.py +6 -7
  58. waldiez/exporting/flow/utils/linting.py +25 -9
  59. waldiez/exporting/flow/utils/logging.py +2 -2
  60. waldiez/exporting/models/exporter.py +8 -8
  61. waldiez/exporting/models/processor.py +5 -5
  62. waldiez/exporting/tools/exporter.py +2 -2
  63. waldiez/exporting/tools/processor.py +7 -4
  64. waldiez/io/__init__.py +8 -4
  65. waldiez/io/_ws.py +10 -6
  66. waldiez/io/models/constants.py +10 -10
  67. waldiez/io/models/content/audio.py +1 -0
  68. waldiez/io/models/content/base.py +20 -18
  69. waldiez/io/models/content/file.py +1 -0
  70. waldiez/io/models/content/image.py +1 -0
  71. waldiez/io/models/content/text.py +1 -0
  72. waldiez/io/models/content/video.py +1 -0
  73. waldiez/io/models/user_input.py +10 -5
  74. waldiez/io/models/user_response.py +17 -16
  75. waldiez/io/mqtt.py +18 -31
  76. waldiez/io/redis.py +18 -22
  77. waldiez/io/structured.py +52 -53
  78. waldiez/io/utils.py +3 -0
  79. waldiez/io/ws.py +5 -1
  80. waldiez/logger.py +16 -3
  81. waldiez/models/agents/__init__.py +3 -0
  82. waldiez/models/agents/agent/agent.py +23 -16
  83. waldiez/models/agents/agent/agent_data.py +25 -22
  84. waldiez/models/agents/agent/code_execution.py +9 -11
  85. waldiez/models/agents/agent/termination_message.py +10 -12
  86. waldiez/models/agents/agent/update_system_message.py +2 -4
  87. waldiez/models/agents/agents.py +8 -8
  88. waldiez/models/agents/assistant/assistant.py +6 -3
  89. waldiez/models/agents/assistant/assistant_data.py +2 -2
  90. waldiez/models/agents/captain/captain_agent.py +7 -4
  91. waldiez/models/agents/captain/captain_agent_data.py +5 -7
  92. waldiez/models/agents/doc_agent/doc_agent.py +7 -4
  93. waldiez/models/agents/doc_agent/doc_agent_data.py +9 -10
  94. waldiez/models/agents/doc_agent/rag_query_engine.py +10 -12
  95. waldiez/models/agents/extra_requirements.py +3 -3
  96. waldiez/models/agents/group_manager/group_manager.py +12 -7
  97. waldiez/models/agents/group_manager/group_manager_data.py +13 -12
  98. waldiez/models/agents/group_manager/speakers.py +17 -19
  99. waldiez/models/agents/rag_user_proxy/rag_user_proxy.py +7 -4
  100. waldiez/models/agents/rag_user_proxy/rag_user_proxy_data.py +4 -1
  101. waldiez/models/agents/rag_user_proxy/retrieve_config.py +69 -63
  102. waldiez/models/agents/rag_user_proxy/vector_db_config.py +19 -19
  103. waldiez/models/agents/reasoning/reasoning_agent.py +7 -4
  104. waldiez/models/agents/reasoning/reasoning_agent_data.py +3 -2
  105. waldiez/models/agents/reasoning/reasoning_agent_reason_config.py +8 -8
  106. waldiez/models/agents/user_proxy/user_proxy.py +6 -3
  107. waldiez/models/agents/user_proxy/user_proxy_data.py +1 -1
  108. waldiez/models/chat/chat.py +27 -20
  109. waldiez/models/chat/chat_data.py +22 -19
  110. waldiez/models/chat/chat_message.py +9 -9
  111. waldiez/models/chat/chat_nested.py +9 -9
  112. waldiez/models/chat/chat_summary.py +6 -6
  113. waldiez/models/common/__init__.py +2 -0
  114. waldiez/models/common/ag2_version.py +2 -0
  115. waldiez/models/common/dict_utils.py +8 -6
  116. waldiez/models/common/handoff.py +18 -17
  117. waldiez/models/common/method_utils.py +7 -7
  118. waldiez/models/common/naming.py +49 -0
  119. waldiez/models/flow/flow.py +11 -6
  120. waldiez/models/flow/flow_data.py +23 -17
  121. waldiez/models/flow/info.py +3 -3
  122. waldiez/models/flow/naming.py +2 -1
  123. waldiez/models/model/_aws.py +11 -13
  124. waldiez/models/model/_llm.py +5 -0
  125. waldiez/models/model/_price.py +2 -4
  126. waldiez/models/model/extra_requirements.py +1 -3
  127. waldiez/models/model/model.py +2 -2
  128. waldiez/models/model/model_data.py +21 -21
  129. waldiez/models/tool/extra_requirements.py +2 -4
  130. waldiez/models/tool/predefined/_duckduckgo.py +1 -0
  131. waldiez/models/tool/predefined/_email.py +1 -0
  132. waldiez/models/tool/predefined/_google.py +1 -0
  133. waldiez/models/tool/predefined/_perplexity.py +1 -0
  134. waldiez/models/tool/predefined/_searxng.py +1 -0
  135. waldiez/models/tool/predefined/_tavily.py +1 -0
  136. waldiez/models/tool/predefined/_wikipedia.py +1 -0
  137. waldiez/models/tool/predefined/_youtube.py +1 -0
  138. waldiez/models/tool/tool.py +8 -5
  139. waldiez/models/tool/tool_data.py +2 -2
  140. waldiez/models/waldiez.py +152 -4
  141. waldiez/runner.py +11 -5
  142. waldiez/running/async_utils.py +192 -0
  143. waldiez/running/base_runner.py +117 -264
  144. waldiez/running/dir_utils.py +52 -0
  145. waldiez/running/environment.py +10 -44
  146. waldiez/running/events_mixin.py +252 -0
  147. waldiez/running/exceptions.py +20 -0
  148. waldiez/running/gen_seq_diagram.py +18 -15
  149. waldiez/running/io_utils.py +216 -0
  150. waldiez/running/protocol.py +11 -5
  151. waldiez/running/requirements_mixin.py +65 -0
  152. waldiez/running/results_mixin.py +926 -0
  153. waldiez/running/standard_runner.py +22 -25
  154. waldiez/running/step_by_step/breakpoints_mixin.py +192 -60
  155. waldiez/running/step_by_step/command_handler.py +3 -0
  156. waldiez/running/step_by_step/events_processor.py +194 -14
  157. waldiez/running/step_by_step/step_by_step_models.py +110 -43
  158. waldiez/running/step_by_step/step_by_step_runner.py +107 -57
  159. waldiez/running/subprocess_runner/__base__.py +9 -1
  160. waldiez/running/subprocess_runner/_async_runner.py +5 -3
  161. waldiez/running/subprocess_runner/_sync_runner.py +6 -2
  162. waldiez/running/subprocess_runner/runner.py +39 -23
  163. waldiez/running/timeline_processor.py +1 -1
  164. waldiez/utils/__init__.py +2 -0
  165. waldiez/utils/conflict_checker.py +4 -4
  166. waldiez/utils/python_manager.py +415 -0
  167. waldiez/ws/_file_handler.py +18 -18
  168. waldiez/ws/_mock.py +2 -1
  169. waldiez/ws/cli.py +36 -12
  170. waldiez/ws/client_manager.py +35 -27
  171. waldiez/ws/errors.py +3 -0
  172. waldiez/ws/models.py +43 -52
  173. waldiez/ws/reloader.py +12 -4
  174. waldiez/ws/server.py +85 -55
  175. waldiez/ws/session_manager.py +8 -9
  176. waldiez/ws/session_stats.py +1 -1
  177. waldiez/ws/utils.py +4 -1
  178. {waldiez-0.6.0.dist-info → waldiez-0.6.1.dist-info}/METADATA +82 -93
  179. waldiez-0.6.1.dist-info/RECORD +254 -0
  180. waldiez/running/post_run.py +0 -186
  181. waldiez/running/pre_run.py +0 -281
  182. waldiez/running/run_results.py +0 -14
  183. waldiez/running/utils.py +0 -625
  184. waldiez-0.6.0.dist-info/RECORD +0 -251
  185. {waldiez-0.6.0.dist-info → waldiez-0.6.1.dist-info}/WHEEL +0 -0
  186. {waldiez-0.6.0.dist-info → waldiez-0.6.1.dist-info}/entry_points.txt +0 -0
  187. {waldiez-0.6.0.dist-info → waldiez-0.6.1.dist-info}/licenses/LICENSE +0 -0
  188. {waldiez-0.6.0.dist-info → waldiez-0.6.1.dist-info}/licenses/NOTICE.md +0 -0
@@ -1,11 +1,20 @@
1
1
  # SPDX-License-Identifier: Apache-2.0.
2
2
  # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
+
3
4
  # pylint: disable=unused-argument,no-self-use
5
+ # pyright: reportMissingTypeStubs=false, reportImportCycles=false
6
+ # pyright: reportDeprecated=false, reportUnknownMemberType=false
7
+ # pyright: reportUnknownVariableType=false, reportUnknownArgumentType=false
8
+
4
9
  """Command handler for step-by-step execution."""
5
10
 
6
- from typing import TYPE_CHECKING, Any, Union
11
+ import inspect
12
+ from collections.abc import Mapping, Sequence
13
+ from dataclasses import asdict, is_dataclass
14
+ from typing import TYPE_CHECKING, Any, Optional, Union
7
15
 
8
16
  if TYPE_CHECKING:
17
+ from autogen.agentchat import ConversableAgent # type: ignore
9
18
  from autogen.events.base_event import BaseEvent # type: ignore
10
19
  from autogen.messages.base_message import BaseMessage # type: ignore
11
20
 
@@ -21,7 +30,9 @@ class EventProcessor:
21
30
  self.runner = runner
22
31
 
23
32
  def process_event(
24
- self, event: Union["BaseEvent", "BaseMessage"]
33
+ self,
34
+ event: Union["BaseEvent", "BaseMessage"],
35
+ agents: list["ConversableAgent"],
25
36
  ) -> dict[str, Any]:
26
37
  """Shared logic for both sync and async event processing.
27
38
 
@@ -29,6 +40,8 @@ class EventProcessor:
29
40
  ----------
30
41
  event : BaseEvent | BaseMessage
31
42
  The event to process.
43
+ agents : list[ConversableAgent]
44
+ The workflow's known agents.
32
45
 
33
46
  Returns
34
47
  -------
@@ -45,19 +58,20 @@ class EventProcessor:
45
58
  self._update_participant_info(event_info)
46
59
  self._manage_event_history(event_info)
47
60
  self._check_for_input_request(event_info)
61
+ self._add_agents_info(event_info, agents)
48
62
 
49
63
  should_break = self.runner.should_break_on_event(
50
- event, self.runner.step_mode
64
+ event, sender_only=True
51
65
  )
52
66
 
53
67
  return {
54
- "action": "continue",
55
- "should_break": should_break,
68
+ "action": "break" if should_break else "continue",
56
69
  "event_info": event_info,
57
70
  }
58
71
 
59
72
  def _create_event_info(
60
- self, event: Union["BaseEvent", "BaseMessage"]
73
+ self,
74
+ event: Union["BaseEvent", "BaseMessage"],
61
75
  ) -> dict[str, Any]:
62
76
  """Create event info dictionary from event object.
63
77
 
@@ -92,7 +106,7 @@ class EventProcessor:
92
106
  if not event_info["sender"] or not event_info["recipient"]:
93
107
  self._extract_participants_from_content(event_info)
94
108
 
95
- self._handle_group_chat_speaker(event_info)
109
+ self._check_for_event_speaker(event_info)
96
110
  self._extract_participants_from_direct_content(event_info)
97
111
 
98
112
  # Update last known participants
@@ -115,14 +129,14 @@ class EventProcessor:
115
129
  and "chat_info" in content
116
130
  and isinstance(content["chat_info"], dict)
117
131
  ):
118
- content = content.get("chat_info", {}) # pyright: ignore
132
+ content = content.get("chat_info", {})
119
133
 
120
134
  if not event_info["sender"] and "sender" in content:
121
135
  event_info["sender"] = content["sender"]
122
136
  if not event_info["recipient"] and "recipient" in content:
123
137
  event_info["recipient"] = content["recipient"]
124
138
 
125
- def _handle_group_chat_speaker(self, event_info: dict[str, Any]) -> None:
139
+ def _check_for_event_speaker(self, event_info: dict[str, Any]) -> None:
126
140
  """Handle speaker information for group chat events.
127
141
 
128
142
  Parameters
@@ -130,11 +144,7 @@ class EventProcessor:
130
144
  event_info : dict[str, Any]
131
145
  Event information dictionary to update.
132
146
  """
133
- if (
134
- event_info.get("type") == "group_chat_run_chat"
135
- and "content" in event_info
136
- and isinstance(event_info["content"], dict)
137
- ):
147
+ if "content" in event_info and isinstance(event_info["content"], dict):
138
148
  content = event_info.get("content", {})
139
149
  speaker = content.get("speaker")
140
150
  if isinstance(speaker, str) and speaker:
@@ -197,3 +207,173 @@ class EventProcessor:
197
207
  event_info["recipient"] = sender
198
208
  self.runner.last_sender = recipient
199
209
  self.runner.last_recipient = sender
210
+
211
+ @staticmethod
212
+ def _get_agent_dump(
213
+ agent: Optional["ConversableAgent"],
214
+ ) -> dict[str, Any] | None:
215
+ if not agent:
216
+ return None
217
+ dump = {
218
+ name: _trimmed(value)
219
+ for name, value in vars(agent).items()
220
+ if not name.startswith("_")
221
+ and not inspect.ismethod(value)
222
+ and not inspect.isfunction(value)
223
+ and name.upper() != name
224
+ and name != "chat_messages"
225
+ }
226
+ for attr_key in ["name", "description", "system_message"]:
227
+ attr_val = getattr(agent, attr_key)
228
+ if attr_val:
229
+ dump[attr_key] = _trimmed(attr_val)
230
+ try:
231
+ dump["chat_messages"] = {
232
+ _to_json_key(agent): [_trimmed(message) for message in messages]
233
+ for _agent, messages in agent.chat_messages.items()
234
+ }
235
+ except BaseException: # pylint: disable=broad-exception-caught
236
+ pass
237
+ dump["cost"] = {
238
+ "actual": agent.get_actual_usage(),
239
+ "total": agent.get_total_usage(),
240
+ }
241
+ return dump
242
+
243
+ def _add_agents_info(
244
+ self, event_info: dict[str, Any], agents: list["ConversableAgent"]
245
+ ) -> None:
246
+ """Add agents info."""
247
+ sender = event_info.get("sender", self.runner.last_sender)
248
+ recipient = event_info.get("recipient", self.runner.last_recipient)
249
+
250
+ agent_map = {agent.name: agent for agent in agents}
251
+
252
+ sender_agent = agent_map.get(sender)
253
+ recipient_agent = agent_map.get(recipient)
254
+
255
+ ordered_agents: list["ConversableAgent"] = []
256
+ seen: set[str] = set()
257
+
258
+ for a in (sender_agent, recipient_agent):
259
+ if a and a.name not in seen:
260
+ ordered_agents.append(a)
261
+ seen.add(a.name)
262
+
263
+ for a in agents:
264
+ if a.name not in seen:
265
+ ordered_agents.append(a)
266
+ seen.add(a.name)
267
+
268
+ event_info["agents"] = {
269
+ "sender": self._get_agent_dump(sender_agent),
270
+ "recipient": self._get_agent_dump(recipient_agent),
271
+ "all": [self._get_agent_dump(a) for a in ordered_agents if a],
272
+ }
273
+
274
+
275
+ # pylint: disable=too-complex,too-many-return-statements,
276
+ # pylint: disable=broad-exception-caught,too-complex,too-many-branches
277
+ def _trimmed( # noqa: C901
278
+ value: Any, max_len: int = 200, _depth: int = 0, _max_depth: int = 10
279
+ ) -> Any:
280
+ """Recursively trim values for serialization."""
281
+ if _depth >= _max_depth:
282
+ # Hard stop
283
+ s = str(value)
284
+ return (s[:max_len] + "...") if len(s) > max_len else s
285
+
286
+ if isinstance(value, str):
287
+ return value[:max_len] + "..." if len(value) > max_len else value
288
+
289
+ if value is None or isinstance(value, (int, float, bool)):
290
+ return value
291
+
292
+ if isinstance(value, (bytes, bytearray)):
293
+ try:
294
+ s = value.decode("utf-8", errors="replace")
295
+ return s[:max_len] + "..." if len(s) > max_len else s
296
+ except Exception:
297
+ return f"<{type(value).__name__}> {len(value)} bytes"
298
+
299
+ if callable(value):
300
+ # pylint: disable=too-many-try-statements
301
+ try:
302
+ name = getattr(value, "__name__", repr(value))
303
+ module = getattr(value, "__module__", "")
304
+ if module:
305
+ s = f"<callable> {module}.{name}"
306
+ else:
307
+ s = f"<callable> {name}"
308
+ return s[:max_len] + "..." if len(s) > max_len else s
309
+ except Exception:
310
+ return "<callable>"
311
+
312
+ if _is_dataclass_instance(value):
313
+ try:
314
+ value = asdict(value)
315
+ except Exception:
316
+ s = str(value)
317
+ return (s[:max_len] + "...") if len(s) > max_len else s
318
+
319
+ if isinstance(value, Mapping):
320
+ out: dict[str, Any] = {}
321
+ for k, v in value.items():
322
+ jk = _to_json_key(k, max_len)
323
+ out[jk] = _trimmed(v, max_len, _depth + 1, _max_depth)
324
+ return out
325
+ if isinstance(value, (set, frozenset)):
326
+ items = [_trimmed(it, max_len, _depth + 1, _max_depth) for it in value]
327
+ try:
328
+ items.sort(key=repr)
329
+ except Exception:
330
+ pass
331
+ return items
332
+
333
+ if isinstance(value, Sequence) and not isinstance(
334
+ value, (str, bytes, bytearray)
335
+ ):
336
+ return [_trimmed(it, max_len, _depth + 1, _max_depth) for it in value]
337
+ if hasattr(value, "__dict__"):
338
+ try:
339
+ return _trimmed(
340
+ {
341
+ k: v
342
+ for k, v in vars(value).items()
343
+ if not str(k).startswith("_") and str(k) != "key"
344
+ },
345
+ max_len,
346
+ _depth + 1,
347
+ _max_depth,
348
+ )
349
+ except Exception:
350
+ pass
351
+
352
+ # Fallback: stringify and trim
353
+ try:
354
+ s = str(value)
355
+ return s[:max_len] + "..." if len(s) > max_len else s
356
+ except Exception:
357
+ return None
358
+
359
+
360
+ def _is_dataclass_instance(obj: Any) -> bool:
361
+ """Check if an object is an instance of a dataclass."""
362
+ return is_dataclass(obj) and not isinstance(obj, type)
363
+
364
+
365
+ def _to_json_key(key: Any, max_len: int = 200) -> str:
366
+ """Convert dict key to a JSON-legal key."""
367
+ if key is None or isinstance(key, (bool, int, float)):
368
+ return str(key)
369
+
370
+ if isinstance(key, str):
371
+ return key[:max_len] + "..." if len(key) > max_len else key
372
+
373
+ for _key in ["name", "_name", "__name__"]:
374
+ if hasattr(key, _key):
375
+ return str(getattr(key, _key))
376
+
377
+ # Non-primitive key: include type to avoid collisions like 1 vs "1"
378
+ s = f"<{type(key).__name__}> {repr(key)}"
379
+ return s[:max_len] + "..." if len(s) > max_len else s
@@ -1,6 +1,9 @@
1
1
  # SPDX-License-Identifier: Apache-2.0.
2
2
  # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
+
3
4
  # pylint: disable=unused-argument,disable=line-too-long
5
+ # pyright: reportUnusedParameter=false, reportUnnecessaryIsInstance=false
6
+ # pyright: reportDeprecated=false
4
7
  # flake8: noqa: E501
5
8
  """Step-by-step execution models for Waldiez."""
6
9
 
@@ -9,6 +12,7 @@ from enum import Enum
9
12
  from typing import Annotated, Any, Literal, Union
10
13
 
11
14
  from pydantic import BaseModel, Field, ValidationInfo, field_validator
15
+ from typing_extensions import override
12
16
 
13
17
 
14
18
  class WaldiezBreakpointType(Enum):
@@ -58,7 +62,7 @@ class WaldiezBreakpoint(BaseModel):
58
62
 
59
63
  type: WaldiezBreakpointType
60
64
  event_type: str | None = None # Required for EVENT and AGENT_EVENT
61
- agent_name: str | None = None # Required for AGENT and AGENT_EVENT
65
+ agent: str | None = None # Required for AGENT and AGENT_EVENT
62
66
  description: str | None = None # Human-readable description
63
67
 
64
68
  # noinspection PyNestedDecorators,PyUnusedLocal
@@ -93,48 +97,50 @@ class WaldiezBreakpoint(BaseModel):
93
97
 
94
98
  # Basic validation - event types should be alphanumeric with underscores
95
99
  if not re.match(r"^[a-zA-Z][a-zA-Z0-9_]*$", v):
96
- raise ValueError(
100
+ msg = (
97
101
  "Invalid breakpoint format. Event type must start with a letter and contain only "
98
102
  "letters, numbers, and underscores"
99
103
  )
104
+ raise ValueError(msg)
100
105
  return v
101
106
 
102
107
  # noinspection PyNestedDecorators,PyUnusedLocal
103
- @field_validator("agent_name")
108
+ @field_validator("agent")
104
109
  @classmethod
105
- def validate_agent_name(
110
+ def validate_agent(
106
111
  cls,
107
112
  v: str | None,
108
113
  info: ValidationInfo,
109
114
  ) -> str | None:
110
- """Validate agent name format.
115
+ """Validate agent name/id format.
111
116
 
112
117
  Parameters
113
118
  ----------
114
119
  v : str | None
115
- The agent name to validate.
120
+ The agent name or id to validate.
116
121
  info : ValidationInfo
117
122
  Validation context information.
118
123
 
119
124
  Returns
120
125
  -------
121
126
  str | None
122
- The validated agent name or None if not provided.
127
+ The validated agent or None if not provided.
123
128
 
124
129
  Raises
125
130
  ------
126
131
  ValueError
127
- If the agent name format is invalid.
132
+ If the agent format is invalid.
128
133
  """
129
134
  if v is None:
130
135
  return v
131
136
 
132
- # Agent names should not be empty or just whitespace
137
+ # Agent name/id should not be empty or just whitespace
133
138
  if not v.strip():
134
- raise ValueError("Agent name cannot be empty or just whitespace")
139
+ raise ValueError("Agent cannot be empty or just whitespace")
135
140
 
136
141
  return v.strip()
137
142
 
143
+ @override
138
144
  def model_post_init(self, __context: Any, /) -> None:
139
145
  """Validate breakpoint consistency after initialization.
140
146
 
@@ -146,28 +152,31 @@ class WaldiezBreakpoint(BaseModel):
146
152
  if self.type == WaldiezBreakpointType.EVENT and not self.event_type:
147
153
  raise ValueError("EVENT breakpoints require an event_type")
148
154
 
149
- if self.type == WaldiezBreakpointType.AGENT and not self.agent_name:
155
+ if self.type == WaldiezBreakpointType.AGENT and not self.agent:
150
156
  raise ValueError("AGENT breakpoints require an agent_name")
151
157
 
152
158
  if self.type == WaldiezBreakpointType.AGENT_EVENT:
153
- if not self.event_type or not self.agent_name:
154
- raise ValueError(
159
+ if not self.event_type or not self.agent:
160
+ msg = (
155
161
  "AGENT_EVENT breakpoints require both"
156
162
  " event_type and agent_name"
157
163
  )
164
+ raise ValueError(msg)
158
165
 
166
+ @override
159
167
  def __hash__(self) -> int:
160
168
  """Get the hash value for the breakpoint."""
161
- return hash((self.type, self.event_type, self.agent_name))
169
+ return hash((self.type, self.event_type, self.agent))
162
170
 
171
+ @override
163
172
  def __str__(self) -> str:
164
173
  """Get the string representation for display."""
165
174
  if self.type == WaldiezBreakpointType.EVENT:
166
175
  return f"event:{self.event_type}"
167
176
  if self.type == WaldiezBreakpointType.AGENT:
168
- return f"agent:{self.agent_name}"
177
+ return f"agent:{self.agent}"
169
178
  if self.type == WaldiezBreakpointType.AGENT_EVENT:
170
- return f"{self.agent_name}:{self.event_type}"
179
+ return f"{self.agent}:{self.event_type}"
171
180
  # else: # ALL
172
181
  return "all"
173
182
 
@@ -194,7 +203,7 @@ class WaldiezBreakpoint(BaseModel):
194
203
  ValueError
195
204
  If the breakpoint string format is invalid.
196
205
  """
197
- if not breakpoint_str or not isinstance( # pyright: ignore
206
+ if not breakpoint_str or not isinstance(
198
207
  breakpoint_str,
199
208
  str,
200
209
  ):
@@ -213,50 +222,109 @@ class WaldiezBreakpoint(BaseModel):
213
222
  event_type = breakpoint_str[6:] # Remove "event:" prefix
214
223
  if not event_type:
215
224
  raise ValueError("Event type cannot be empty after 'event:'")
216
- return cls(type=WaldiezBreakpointType.EVENT, event_type=event_type)
225
+ return cls(
226
+ type=WaldiezBreakpointType.EVENT,
227
+ event_type=event_type,
228
+ )
217
229
 
218
230
  if breakpoint_str.startswith("agent:"):
219
- agent_name = breakpoint_str[6:] # Remove "agent:" prefix
220
- if not agent_name:
221
- raise ValueError("Agent name cannot be empty after 'agent:'")
222
- return cls(type=WaldiezBreakpointType.AGENT, agent_name=agent_name)
231
+ agent = breakpoint_str[6:] # Remove "agent:" prefix
232
+ if not agent:
233
+ raise ValueError(
234
+ "Agent identifier cannot be empty after 'agent:'"
235
+ )
236
+ return cls(
237
+ type=WaldiezBreakpointType.AGENT,
238
+ agent=agent,
239
+ )
223
240
 
224
241
  if ":" in breakpoint_str and not breakpoint_str.startswith(
225
242
  ("event:", "agent:")
226
243
  ):
227
- # Format: "agent_name:event_type"
244
+ # Format: "agent:event_type"
228
245
  parts = breakpoint_str.split(":", 1)
229
246
  if len(parts) != 2:
230
247
  raise ValueError("Invalid agent:event format")
231
248
 
232
- agent_name, event_type = parts
233
- if not agent_name or not event_type:
249
+ agent, event_type = parts
250
+ if not agent or not event_type:
234
251
  raise ValueError(
235
- "Both agent name and event type must be specified"
252
+ "Both agent identifier and event type must be specified"
236
253
  )
237
254
 
238
255
  return cls(
239
256
  type=WaldiezBreakpointType.AGENT_EVENT,
240
- agent_name=agent_name,
257
+ agent=agent,
241
258
  event_type=event_type,
242
259
  )
243
-
244
- # Default to event type - but validate it's reasonable
245
260
  if ":" in breakpoint_str:
246
- raise ValueError(
261
+ msg = (
247
262
  "Invalid breakpoint format. Use 'event:type', 'agent:name', "
248
263
  "'agent:event', or 'all'"
249
264
  )
265
+ raise ValueError(msg)
266
+
267
+ return cls(
268
+ type=WaldiezBreakpointType.EVENT,
269
+ event_type=breakpoint_str,
270
+ )
250
271
 
251
- return cls(type=WaldiezBreakpointType.EVENT, event_type=breakpoint_str)
272
+ def matches_agent(
273
+ self,
274
+ event: dict[str, Any],
275
+ agent_id_to_name: dict[str, str],
276
+ sender_only: bool,
277
+ ) -> bool:
278
+ """Check if the event's sender or recipient matches the breakpoint's agent.
252
279
 
253
- def matches(self, event: dict[str, Any]) -> bool:
280
+ Parameters
281
+ ----------
282
+ event : dict[str, Any]
283
+ The event to check against.
284
+ agent_id_to_name : dict[str, str]
285
+ The mapping between an agent's id and its name.
286
+ sender_only : dict[str, Any]
287
+ Only check for sender match.
288
+
289
+ Returns
290
+ -------
291
+ bool
292
+ True if the event's sender or recipient matches the breakpoint's agent.
293
+ """
294
+ if not self.agent:
295
+ return False
296
+ # Normalize both the event's sender/recipient and the breakpoint's agent
297
+ _event_sender = event.get("sender", "")
298
+ event_sender = agent_id_to_name.get(_event_sender, _event_sender)
299
+ _event_recipient = event.get("recipient", "")
300
+ event_recipient = agent_id_to_name.get(
301
+ _event_recipient, _event_recipient
302
+ )
303
+ breakpoint_agent = (
304
+ agent_id_to_name.get(self.agent, self.agent) if self.agent else ""
305
+ )
306
+ if not breakpoint_agent:
307
+ return False
308
+ if sender_only:
309
+ return breakpoint_agent == event_sender
310
+ return breakpoint_agent in (event_sender, event_recipient)
311
+
312
+ def matches(
313
+ self,
314
+ event: dict[str, Any],
315
+ agent_id_to_name: dict[str, str],
316
+ sender_only: bool,
317
+ ) -> bool:
254
318
  """Check if this breakpoint matches the given event.
255
319
 
256
320
  Parameters
257
321
  ----------
258
322
  event : dict[str, Any]
259
323
  The event to check against.
324
+ agent_id_to_name : dict[str, str]
325
+ The mapping between an agent's id and its name.
326
+ sender_only : dict[str, Any]
327
+ On agent events, only check for sender match.
260
328
 
261
329
  Returns
262
330
  -------
@@ -270,22 +338,22 @@ class WaldiezBreakpoint(BaseModel):
270
338
  return event.get("type") == self.event_type
271
339
 
272
340
  if self.type == WaldiezBreakpointType.AGENT:
273
- return (
274
- event.get("sender") == self.agent_name
275
- or event.get("recipient") == self.agent_name
341
+ return self.matches_agent(
342
+ event, agent_id_to_name, sender_only=sender_only
276
343
  )
277
344
 
278
345
  if self.type == WaldiezBreakpointType.AGENT_EVENT:
279
- return event.get("type") == self.event_type and (
280
- event.get("sender") == self.agent_name
281
- or event.get("recipient") == self.agent_name
346
+ event_type = event.get("type", "")
347
+ if event_type != self.event_type:
348
+ return False
349
+ return self.matches_agent(
350
+ event, agent_id_to_name, sender_only=sender_only
282
351
  )
283
352
 
284
353
  # noinspection PyUnreachableCode
285
354
  return False
286
355
 
287
356
 
288
- # Enhanced configuration class for runtime settings
289
357
  class WaldiezDebugConfig(BaseModel):
290
358
  """Configuration for debug session settings."""
291
359
 
@@ -296,7 +364,6 @@ class WaldiezDebugConfig(BaseModel):
296
364
  command_timeout_seconds: float = Field(default=300.0, gt=0)
297
365
 
298
366
 
299
- # Rest of the existing message classes remain the same...
300
367
  class WaldiezDebugBreakpointsList(BaseModel):
301
368
  """Debug breakpoints message."""
302
369
 
@@ -314,7 +381,6 @@ class WaldiezDebugBreakpointsList(BaseModel):
314
381
  try:
315
382
  result.append(WaldiezBreakpoint.from_string(bp))
316
383
  except ValueError:
317
- # Skip invalid breakpoints rather than failing
318
384
  continue
319
385
  else:
320
386
  result.append(bp)
@@ -325,7 +391,7 @@ class WaldiezDebugBreakpointAdded(BaseModel):
325
391
  """Debug breakpoint added message."""
326
392
 
327
393
  type: Literal["debug_breakpoint_added"] = "debug_breakpoint_added"
328
- breakpoint: Union[str, WaldiezBreakpoint]
394
+ breakpoint: str | WaldiezBreakpoint
329
395
 
330
396
  @property
331
397
  def breakpoint_object(self) -> WaldiezBreakpoint:
@@ -339,7 +405,7 @@ class WaldiezDebugBreakpointRemoved(BaseModel):
339
405
  """Debug breakpoint removed message."""
340
406
 
341
407
  type: Literal["debug_breakpoint_removed"] = "debug_breakpoint_removed"
342
- breakpoint: Union[str, WaldiezBreakpoint]
408
+ breakpoint: str | WaldiezBreakpoint
343
409
 
344
410
  @property
345
411
  def breakpoint_object(self) -> WaldiezBreakpoint:
@@ -423,6 +489,7 @@ class WaldiezDebugBreakpointCleared(BaseModel):
423
489
  message: str
424
490
 
425
491
 
492
+ # pylint: disable=invalid-name
426
493
  WaldiezDebugMessage = Annotated[
427
494
  Union[
428
495
  WaldiezDebugPrint,