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

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

Potentially problematic release.


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

Files changed (192) hide show
  1. waldiez/__init__.py +1 -1
  2. waldiez/_version.py +1 -1
  3. waldiez/cli.py +19 -7
  4. waldiez/cli_extras/jupyter.py +3 -0
  5. waldiez/cli_extras/runner.py +3 -1
  6. waldiez/cli_extras/studio.py +3 -1
  7. waldiez/exporter.py +9 -3
  8. waldiez/exporting/agent/exporter.py +15 -16
  9. waldiez/exporting/agent/extras/captain_agent_extras.py +6 -6
  10. waldiez/exporting/agent/extras/doc_agent_extras.py +6 -6
  11. waldiez/exporting/agent/extras/group_manager_agent_extas.py +40 -24
  12. waldiez/exporting/agent/extras/group_member_extras.py +6 -5
  13. waldiez/exporting/agent/extras/handoffs/after_work.py +2 -1
  14. waldiez/exporting/agent/extras/handoffs/available.py +2 -1
  15. waldiez/exporting/agent/extras/handoffs/condition.py +3 -2
  16. waldiez/exporting/agent/extras/handoffs/handoff.py +2 -1
  17. waldiez/exporting/agent/extras/handoffs/target.py +7 -4
  18. waldiez/exporting/agent/extras/rag/chroma_extras.py +27 -19
  19. waldiez/exporting/agent/extras/rag/mongo_extras.py +8 -8
  20. waldiez/exporting/agent/extras/rag/pgvector_extras.py +5 -5
  21. waldiez/exporting/agent/extras/rag/qdrant_extras.py +5 -4
  22. waldiez/exporting/agent/extras/rag/vector_db_extras.py +1 -1
  23. waldiez/exporting/agent/extras/rag_user_proxy_agent_extras.py +5 -7
  24. waldiez/exporting/agent/extras/reasoning_agent_extras.py +3 -5
  25. waldiez/exporting/agent/termination.py +1 -0
  26. waldiez/exporting/chats/exporter.py +4 -4
  27. waldiez/exporting/chats/processor.py +1 -2
  28. waldiez/exporting/chats/utils/common.py +89 -48
  29. waldiez/exporting/chats/utils/group.py +9 -9
  30. waldiez/exporting/chats/utils/nested.py +7 -7
  31. waldiez/exporting/chats/utils/sequential.py +1 -1
  32. waldiez/exporting/chats/utils/single.py +2 -2
  33. waldiez/exporting/core/constants.py +3 -1
  34. waldiez/exporting/core/content.py +7 -7
  35. waldiez/exporting/core/context.py +5 -3
  36. waldiez/exporting/core/exporter.py +5 -3
  37. waldiez/exporting/core/exporters.py +2 -2
  38. waldiez/exporting/core/extras/agent_extras/captain_extras.py +2 -2
  39. waldiez/exporting/core/extras/agent_extras/group_manager_extras.py +2 -2
  40. waldiez/exporting/core/extras/agent_extras/rag_user_extras.py +2 -2
  41. waldiez/exporting/core/extras/agent_extras/standard_extras.py +3 -8
  42. waldiez/exporting/core/extras/base.py +7 -5
  43. waldiez/exporting/core/extras/flow_extras.py +4 -5
  44. waldiez/exporting/core/extras/model_extras.py +2 -2
  45. waldiez/exporting/core/extras/path_resolver.py +1 -2
  46. waldiez/exporting/core/extras/serializer.py +13 -11
  47. waldiez/exporting/core/protocols.py +6 -5
  48. waldiez/exporting/core/result.py +25 -28
  49. waldiez/exporting/core/types.py +11 -10
  50. waldiez/exporting/core/utils/llm_config.py +4 -4
  51. waldiez/exporting/core/validation.py +10 -11
  52. waldiez/exporting/flow/execution_generator.py +99 -10
  53. waldiez/exporting/flow/exporter.py +2 -2
  54. waldiez/exporting/flow/factory.py +2 -2
  55. waldiez/exporting/flow/file_generator.py +4 -2
  56. waldiez/exporting/flow/merger.py +5 -3
  57. waldiez/exporting/flow/orchestrator.py +72 -2
  58. waldiez/exporting/flow/utils/common.py +6 -6
  59. waldiez/exporting/flow/utils/importing.py +7 -8
  60. waldiez/exporting/flow/utils/linting.py +25 -9
  61. waldiez/exporting/flow/utils/logging.py +5 -77
  62. waldiez/exporting/models/exporter.py +8 -8
  63. waldiez/exporting/models/processor.py +5 -5
  64. waldiez/exporting/tools/exporter.py +2 -2
  65. waldiez/exporting/tools/processor.py +7 -4
  66. waldiez/io/__init__.py +11 -5
  67. waldiez/io/_ws.py +12 -6
  68. waldiez/io/models/constants.py +10 -10
  69. waldiez/io/models/content/audio.py +1 -0
  70. waldiez/io/models/content/base.py +20 -18
  71. waldiez/io/models/content/file.py +1 -0
  72. waldiez/io/models/content/image.py +1 -0
  73. waldiez/io/models/content/text.py +1 -0
  74. waldiez/io/models/content/video.py +1 -0
  75. waldiez/io/models/user_input.py +10 -5
  76. waldiez/io/models/user_response.py +17 -16
  77. waldiez/io/mqtt.py +18 -31
  78. waldiez/io/redis.py +18 -22
  79. waldiez/io/structured.py +122 -70
  80. waldiez/io/utils.py +19 -10
  81. waldiez/io/ws.py +7 -3
  82. waldiez/logger.py +16 -3
  83. waldiez/models/agents/__init__.py +3 -0
  84. waldiez/models/agents/agent/agent.py +25 -17
  85. waldiez/models/agents/agent/agent_data.py +25 -22
  86. waldiez/models/agents/agent/code_execution.py +9 -11
  87. waldiez/models/agents/agent/termination_message.py +10 -12
  88. waldiez/models/agents/agent/update_system_message.py +2 -4
  89. waldiez/models/agents/agents.py +8 -8
  90. waldiez/models/agents/assistant/assistant.py +6 -3
  91. waldiez/models/agents/assistant/assistant_data.py +2 -2
  92. waldiez/models/agents/captain/captain_agent.py +7 -4
  93. waldiez/models/agents/captain/captain_agent_data.py +5 -7
  94. waldiez/models/agents/doc_agent/doc_agent.py +7 -4
  95. waldiez/models/agents/doc_agent/doc_agent_data.py +9 -10
  96. waldiez/models/agents/doc_agent/rag_query_engine.py +10 -12
  97. waldiez/models/agents/extra_requirements.py +3 -3
  98. waldiez/models/agents/group_manager/group_manager.py +12 -7
  99. waldiez/models/agents/group_manager/group_manager_data.py +13 -12
  100. waldiez/models/agents/group_manager/speakers.py +17 -19
  101. waldiez/models/agents/rag_user_proxy/rag_user_proxy.py +7 -4
  102. waldiez/models/agents/rag_user_proxy/rag_user_proxy_data.py +4 -1
  103. waldiez/models/agents/rag_user_proxy/retrieve_config.py +69 -63
  104. waldiez/models/agents/rag_user_proxy/vector_db_config.py +19 -19
  105. waldiez/models/agents/reasoning/reasoning_agent.py +7 -4
  106. waldiez/models/agents/reasoning/reasoning_agent_data.py +3 -2
  107. waldiez/models/agents/reasoning/reasoning_agent_reason_config.py +8 -8
  108. waldiez/models/agents/user_proxy/user_proxy.py +6 -3
  109. waldiez/models/agents/user_proxy/user_proxy_data.py +1 -1
  110. waldiez/models/chat/chat.py +28 -20
  111. waldiez/models/chat/chat_data.py +22 -21
  112. waldiez/models/chat/chat_message.py +9 -9
  113. waldiez/models/chat/chat_nested.py +9 -9
  114. waldiez/models/chat/chat_summary.py +6 -6
  115. waldiez/models/common/__init__.py +2 -0
  116. waldiez/models/common/ag2_version.py +2 -0
  117. waldiez/models/common/base.py +2 -0
  118. waldiez/models/common/dict_utils.py +8 -6
  119. waldiez/models/common/handoff.py +20 -17
  120. waldiez/models/common/method_utils.py +9 -7
  121. waldiez/models/common/naming.py +49 -0
  122. waldiez/models/flow/flow.py +11 -6
  123. waldiez/models/flow/flow_data.py +23 -17
  124. waldiez/models/flow/info.py +3 -3
  125. waldiez/models/flow/naming.py +2 -1
  126. waldiez/models/model/_aws.py +11 -13
  127. waldiez/models/model/_llm.py +8 -0
  128. waldiez/models/model/_price.py +2 -4
  129. waldiez/models/model/extra_requirements.py +1 -3
  130. waldiez/models/model/model.py +2 -2
  131. waldiez/models/model/model_data.py +21 -21
  132. waldiez/models/tool/extra_requirements.py +2 -4
  133. waldiez/models/tool/predefined/_duckduckgo.py +1 -0
  134. waldiez/models/tool/predefined/_email.py +4 -0
  135. waldiez/models/tool/predefined/_google.py +1 -0
  136. waldiez/models/tool/predefined/_perplexity.py +2 -1
  137. waldiez/models/tool/predefined/_searxng.py +2 -1
  138. waldiez/models/tool/predefined/_tavily.py +1 -0
  139. waldiez/models/tool/predefined/_wikipedia.py +2 -1
  140. waldiez/models/tool/predefined/_youtube.py +1 -0
  141. waldiez/models/tool/tool.py +8 -5
  142. waldiez/models/tool/tool_data.py +2 -2
  143. waldiez/models/waldiez.py +152 -4
  144. waldiez/runner.py +11 -5
  145. waldiez/running/async_utils.py +192 -0
  146. waldiez/running/base_runner.py +155 -241
  147. waldiez/running/dir_utils.py +52 -0
  148. waldiez/running/environment.py +10 -44
  149. waldiez/running/events_mixin.py +252 -0
  150. waldiez/running/exceptions.py +20 -0
  151. waldiez/running/gen_seq_diagram.py +18 -15
  152. waldiez/running/io_utils.py +216 -0
  153. waldiez/running/protocol.py +11 -5
  154. waldiez/running/requirements_mixin.py +65 -0
  155. waldiez/running/results_mixin.py +926 -0
  156. waldiez/running/standard_runner.py +24 -27
  157. waldiez/running/step_by_step/breakpoints_mixin.py +503 -47
  158. waldiez/running/step_by_step/command_handler.py +154 -0
  159. waldiez/running/step_by_step/events_processor.py +379 -0
  160. waldiez/running/step_by_step/step_by_step_models.py +425 -41
  161. waldiez/running/step_by_step/step_by_step_runner.py +437 -382
  162. waldiez/running/subprocess_runner/__base__.py +13 -8
  163. waldiez/running/subprocess_runner/_async_runner.py +6 -4
  164. waldiez/running/subprocess_runner/_sync_runner.py +11 -6
  165. waldiez/running/subprocess_runner/runner.py +48 -23
  166. waldiez/running/timeline_processor.py +1 -1
  167. waldiez/utils/__init__.py +2 -0
  168. waldiez/utils/conflict_checker.py +4 -4
  169. waldiez/utils/python_manager.py +415 -0
  170. waldiez/ws/__init__.py +8 -7
  171. waldiez/ws/_file_handler.py +18 -20
  172. waldiez/ws/_mock.py +75 -0
  173. waldiez/ws/cli.py +58 -10
  174. waldiez/ws/client_manager.py +77 -53
  175. waldiez/ws/errors.py +3 -0
  176. waldiez/ws/models.py +61 -53
  177. waldiez/ws/reloader.py +33 -4
  178. waldiez/ws/server.py +121 -52
  179. waldiez/ws/session_manager.py +8 -9
  180. waldiez/ws/session_stats.py +1 -1
  181. waldiez/ws/utils.py +33 -5
  182. {waldiez-0.5.10.dist-info → waldiez-0.6.1.dist-info}/METADATA +107 -109
  183. waldiez-0.6.1.dist-info/RECORD +254 -0
  184. waldiez/running/post_run.py +0 -180
  185. waldiez/running/pre_run.py +0 -159
  186. waldiez/running/run_results.py +0 -14
  187. waldiez/running/utils.py +0 -511
  188. waldiez-0.5.10.dist-info/RECORD +0 -248
  189. {waldiez-0.5.10.dist-info → waldiez-0.6.1.dist-info}/WHEEL +0 -0
  190. {waldiez-0.5.10.dist-info → waldiez-0.6.1.dist-info}/entry_points.txt +0 -0
  191. {waldiez-0.5.10.dist-info → waldiez-0.6.1.dist-info}/licenses/LICENSE +0 -0
  192. {waldiez-0.5.10.dist-info → waldiez-0.6.1.dist-info}/licenses/NOTICE.md +0 -0
waldiez/io/structured.py CHANGED
@@ -1,16 +1,18 @@
1
1
  # SPDX-License-Identifier: Apache-2.0.
2
2
  # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
3
 
4
+ # pyright: reportMissingTypeStubs=false,reportUnknownVariableType=false
5
+ # pyright: reportUnknownArgumentType=false,reportArgumentType=false
6
+ # pyright: reportUnknownMemberType=false,reportAttributeAccessIssue=false
7
+ # pyright: reportPrivateImportUsage=false
8
+
4
9
  """Structured I/O stream for JSON-based communication over stdin/stdout."""
5
10
 
6
11
  import json
7
- import queue
8
12
  import sys
9
- import threading
10
13
  from getpass import getpass
11
14
  from pathlib import Path
12
15
  from typing import Any
13
- from uuid import uuid4
14
16
 
15
17
  from autogen.events import BaseEvent # type: ignore
16
18
  from autogen.io import IOStream # type: ignore
@@ -23,6 +25,9 @@ from .models import (
23
25
  UserResponse,
24
26
  )
25
27
  from .utils import (
28
+ DEBUG_INPUT_PROMPT,
29
+ START_CHAT_PROMPT,
30
+ MessageType,
26
31
  gen_id,
27
32
  get_image,
28
33
  get_message_dump,
@@ -61,33 +66,57 @@ class StructuredIOStream(IOStream):
61
66
  The data to print.
62
67
  kwargs : Any
63
68
  """
64
- sep = kwargs.get("sep", " ")
65
- end = kwargs.get("end", "\n")
69
+ sep = str(kwargs.get("sep", " "))
70
+ end = str(kwargs.get("end", "\n"))
71
+ flush = bool(kwargs.get("flush", True))
72
+ payload_type = str(kwargs.get("type", "print"))
66
73
  message = sep.join(map(str, args))
67
- is_dumped = is_json_dumped(message)
68
- if is_dumped and end.endswith("\n"):
74
+ if len(args) == 1 and isinstance(args[0], dict):
75
+ message = args[0] # type: ignore
76
+ payload_type_ = message.get("type", payload_type) # type: ignore
77
+ if isinstance(payload_type_, str):
78
+ payload_type = payload_type_
79
+ is_dumped = True
80
+ else:
81
+ is_dumped, message = is_json_dumped(message)
82
+ if is_dumped:
69
83
  # If the message is already JSON-dumped,
70
84
  # let's try not to double dump it
71
- message = json.loads(message)
72
85
  payload: dict[str, Any] = {
73
- "type": "print",
74
- "id": uuid4().hex,
86
+ "id": gen_id(),
75
87
  "timestamp": now(),
76
- "data": message,
88
+ # "data": message,
77
89
  }
90
+ if isinstance(message, dict):
91
+ payload.update(message)
92
+ end = ""
93
+ else:
94
+ payload["data"] = message
95
+ if "type" not in payload:
96
+ payload["type"] = payload_type
97
+ else:
98
+ print_message = PrintMessage(data=message)
99
+ payload = print_message.model_dump(mode="json", fallback=str)
100
+ payload["type"] = payload_type
101
+ dumped = json.dumps(payload, default=str, ensure_ascii=False) + end
102
+ file = kwargs.get("file", None)
103
+ if file and file in [
104
+ sys.stderr,
105
+ sys.__stderr__,
106
+ sys.stdout,
107
+ sys.__stdout__,
108
+ ]:
109
+ print(dumped, file=file, flush=flush)
78
110
  else:
79
- message += end
80
- payload = PrintMessage(
81
- id=uuid4().hex,
82
- timestamp=now(),
83
- data=message,
84
- ).model_dump(mode="json")
85
- flush = kwargs.get("flush", True)
86
- payload_type = kwargs.get("type", "print")
87
- payload["type"] = payload_type
88
- print(json.dumps(payload, default=str), flush=flush)
89
-
90
- def input(self, prompt: str = "", *, password: bool = False) -> str:
111
+ print(dumped, flush=flush)
112
+
113
+ def input(
114
+ self,
115
+ prompt: str = "",
116
+ *,
117
+ password: bool = False,
118
+ request_id: str | None = None,
119
+ ) -> str:
91
120
  """Structured input from stdin.
92
121
 
93
122
  Parameters
@@ -96,25 +125,36 @@ class StructuredIOStream(IOStream):
96
125
  The prompt to display. Defaults to "".
97
126
  password : bool, optional
98
127
  Whether to read a password. Defaults to False.
128
+ request_id : str, optional
129
+ The request id. If not provided, a new will be generated.
99
130
 
100
131
  Returns
101
132
  -------
102
133
  str
103
134
  The line read from the input stream.
104
135
  """
105
- request_id = uuid4().hex
136
+ input_request_id = request_id or gen_id()
106
137
  prompt = prompt or ">"
107
138
  if not prompt or prompt in [">", "> "]: # pragma: no cover
108
139
  # if the prompt is just ">" or "> ",
109
140
  # let's use a more descriptive one
110
- prompt = "Enter your message to start the conversation: "
111
-
112
- self._send_input_request(prompt, request_id, password)
113
- user_input_raw = self._read_user_input(prompt, password, request_id)
114
- response = self._handle_user_input(user_input_raw, request_id)
141
+ prompt = START_CHAT_PROMPT
142
+ input_type = "chat"
143
+ if prompt.strip() == DEBUG_INPUT_PROMPT.strip():
144
+ input_type = "debug"
145
+ self._send_input_request(
146
+ prompt,
147
+ input_request_id,
148
+ password,
149
+ input_type=input_type,
150
+ )
151
+ user_input_raw = self._read_user_input(
152
+ prompt, password, input_request_id
153
+ )
154
+ response = self._handle_user_input(user_input_raw, input_request_id)
115
155
  user_response = response.to_string(
116
156
  uploads_root=self.uploads_root,
117
- base_name=request_id,
157
+ base_name=input_request_id,
118
158
  )
119
159
  return user_response
120
160
 
@@ -140,11 +180,8 @@ class StructuredIOStream(IOStream):
140
180
  content_block["content"] = try_parse_maybe_serialized(
141
181
  inner_content
142
182
  )
143
- print(
144
- json.dumps(message_dump, default=str),
145
- flush=True,
146
- file=sys.stdout,
147
- )
183
+ message_dump["timestamp"] = now()
184
+ print(json.dumps(message_dump, default=str), flush=True)
148
185
 
149
186
  # noinspection PyMethodMayBeStatic
150
187
  # pylint: disable=no-self-use
@@ -152,14 +189,21 @@ class StructuredIOStream(IOStream):
152
189
  self,
153
190
  prompt: str,
154
191
  request_id: str,
155
- password: bool,
192
+ password: bool = False,
193
+ input_type: str = "chat",
156
194
  ) -> None:
195
+ if input_type not in ("chat", "debug"):
196
+ input_type = "chat"
197
+ request_type = (
198
+ "debug_input_request" if input_type == "debug" else "input_request"
199
+ )
157
200
  payload = UserInputRequest(
201
+ type=request_type, # type: ignore
158
202
  request_id=request_id,
159
203
  prompt=prompt,
160
204
  password=password,
161
205
  ).model_dump(mode="json")
162
- print(json.dumps(payload), flush=True)
206
+ print(json.dumps(payload, default=str), flush=True)
163
207
 
164
208
  def _read_user_input(
165
209
  self,
@@ -167,33 +211,15 @@ class StructuredIOStream(IOStream):
167
211
  password: bool,
168
212
  request_id: str,
169
213
  ) -> str:
170
- input_queue: queue.Queue[str] = queue.Queue()
171
-
172
- def read_input() -> None:
173
- """Read user input from stdin."""
174
- try:
175
- user_input = (
176
- getpass(prompt).strip()
177
- if password
178
- else input(prompt).strip()
179
- )
180
- input_queue.put(user_input)
181
- except EOFError:
182
- input_queue.put("")
183
-
184
- input_thread = threading.Thread(target=read_input, daemon=True)
185
- input_thread.start()
186
-
187
214
  try:
188
- return input_queue.get(timeout=self.timeout)
189
- except queue.Empty:
190
- self._send_timeout_message(request_id)
215
+ return (
216
+ getpass(prompt).strip() if password else input(prompt).strip()
217
+ )
218
+ except EOFError:
191
219
  return ""
192
220
  except BaseException as e:
193
221
  self._send_error_message(request_id, str(e))
194
222
  return ""
195
- finally:
196
- input_thread.join(timeout=1)
197
223
 
198
224
  def _send_timeout_message(self, request_id: str) -> None:
199
225
  timeout_payload = {
@@ -294,14 +320,23 @@ class StructuredIOStream(IOStream):
294
320
  UserResponse
295
321
  The structured user response.
296
322
  """
297
- # Load the user input
298
- if user_input.get("request_id") == request_id:
323
+ response_type: MessageType
324
+ _response_type = user_input.get("type", "input_response")
325
+ if _response_type not in ("input_response", "debug_input_response"):
326
+ response_type = "input_response"
327
+ else:
328
+ response_type = _response_type
329
+ if (
330
+ user_input.get("request_id") == request_id
331
+ or response_type == "debug_input_response"
332
+ ):
299
333
  # We have a valid response to our request
300
334
  data = user_input.get("data")
301
335
  if not data:
302
336
  # let's check if text|image keys are sent (outside data)
303
337
  if "image" in user_input or "text" in user_input:
304
338
  return UserResponse(
339
+ type=response_type,
305
340
  request_id=request_id,
306
341
  data=self._format_multimedia_response(
307
342
  request_id=request_id, data=user_input
@@ -309,12 +344,14 @@ class StructuredIOStream(IOStream):
309
344
  )
310
345
  if isinstance(data, list):
311
346
  return self._handle_list_response(
312
- data, # pyright: ignore
347
+ data,
313
348
  request_id=request_id,
349
+ response_type=response_type,
314
350
  )
315
351
  if not data or not isinstance(data, (str, dict)):
316
352
  # No / invalid data provided in the response
317
353
  return UserResponse(
354
+ type=response_type,
318
355
  request_id=request_id,
319
356
  data="",
320
357
  )
@@ -324,20 +361,24 @@ class StructuredIOStream(IOStream):
324
361
  data = self._load_user_input(data)
325
362
  if isinstance(data, dict):
326
363
  return UserResponse(
364
+ type=response_type,
327
365
  data=self._format_multimedia_response(
328
366
  request_id=request_id,
329
- data=data, # pyright: ignore
367
+ data=data,
330
368
  ),
331
369
  request_id=request_id,
332
370
  )
333
371
  # For other types (numbers, bools ,...),
334
372
  # let's just convert to string
335
373
  return UserResponse(
336
- data=str(data), request_id=request_id
374
+ type=response_type,
375
+ data=str(data),
376
+ request_id=request_id,
337
377
  ) # pragma: no cover
338
378
  # This response doesn't match our request_id, log and return empty
339
379
  self._log_mismatched_response(request_id, user_input)
340
380
  return UserResponse(
381
+ type=response_type,
341
382
  request_id=request_id,
342
383
  data="",
343
384
  )
@@ -347,16 +388,18 @@ class StructuredIOStream(IOStream):
347
388
  self,
348
389
  data: list[dict[str, Any]],
349
390
  request_id: str,
391
+ response_type: MessageType,
350
392
  ) -> UserResponse:
351
- if len(data) == 0: # pyright: ignore
393
+ if len(data) == 0:
352
394
  # Empty list, return empty response
353
395
  return UserResponse(
396
+ type=response_type,
354
397
  request_id=request_id,
355
398
  data="",
356
399
  )
357
400
 
358
401
  input_data: list[UserInputData] = []
359
- for entry in data: # pyright: ignore
402
+ for entry in data:
360
403
  # pylint: disable=broad-exception-caught
361
404
  try:
362
405
  content = UserInputData.model_validate(entry)
@@ -367,10 +410,12 @@ class StructuredIOStream(IOStream):
367
410
  if not input_data: # pragma: no cover
368
411
  # No valid data in the list, return empty response
369
412
  return UserResponse(
413
+ type=response_type,
370
414
  request_id=request_id,
371
415
  data="",
372
416
  )
373
417
  return UserResponse(
418
+ type=response_type,
374
419
  request_id=request_id,
375
420
  data=input_data,
376
421
  )
@@ -387,9 +432,16 @@ class StructuredIOStream(IOStream):
387
432
  The response received
388
433
  """
389
434
  # Create a log message
435
+ got_id: str | None = None
436
+ if isinstance(response, dict):
437
+ got_id = response.get("request_id")
438
+ response_str = str(response)
439
+ message = response_str[:100] + (
440
+ "..." if len(response_str) > 100 else ""
441
+ )
390
442
  log_payload: dict[str, Any] = {
391
443
  "type": "warning",
392
- "id": uuid4().hex,
444
+ "id": gen_id(),
393
445
  "timestamp": now(),
394
446
  "data": {
395
447
  "message": (
@@ -398,8 +450,8 @@ class StructuredIOStream(IOStream):
398
450
  ),
399
451
  "details": {
400
452
  "expected_id": expected_id,
401
- "received": str(response)[:100]
402
- + ("..." if len(str(response)) > 100 else ""),
453
+ "received_id": got_id,
454
+ "message": message,
403
455
  },
404
456
  },
405
457
  }
@@ -428,7 +480,7 @@ class StructuredIOStream(IOStream):
428
480
  result: list[str] = []
429
481
  if "content" in data and isinstance(data["content"], dict):
430
482
  return self._format_multimedia_response(
431
- data=data["content"], # pyright: ignore
483
+ data=data["content"],
432
484
  request_id=request_id,
433
485
  )
434
486
 
waldiez/io/utils.py CHANGED
@@ -1,6 +1,8 @@
1
1
  # SPDX-License-Identifier: Apache-2.0.
2
2
  # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
3
 
4
+ # pyright: reportMissingTypeStubs=false,reportDeprecated=false
5
+
4
6
  """Utility functions for the waldiez.io package."""
5
7
 
6
8
  import ast
@@ -14,9 +16,17 @@ from autogen.agentchat.contrib.img_utils import get_pil_image # type: ignore
14
16
  from autogen.events import BaseEvent # type: ignore
15
17
  from autogen.messages import BaseMessage # type: ignore
16
18
 
19
+ DEBUG_INPUT_PROMPT = (
20
+ # cspell: disable-next-line
21
+ "[Step] (c)ontinue, (r)un, (q)uit, (i)nfo, (h)elp, (st)ats: "
22
+ )
23
+ START_CHAT_PROMPT = "Enter your message to start the conversation: "
24
+
17
25
  MessageType = Literal[
18
26
  "input_request",
19
27
  "input_response",
28
+ "debug_input_request",
29
+ "debug_input_response",
20
30
  "print",
21
31
  "input",
22
32
  ]
@@ -131,26 +141,25 @@ def get_image(
131
141
  return image_data
132
142
 
133
143
 
134
- def is_json_dumped(value: str) -> bool:
135
- """Check if a string is JSON-dumped.
144
+ def is_json_dumped(value: Any) -> tuple[bool, Any]:
145
+ """Check if a value is JSON-dumped.
136
146
 
137
147
  Parameters
138
148
  ----------
139
- value : str
140
- The string to check.
149
+ value : Any
150
+ The value to check.
141
151
 
142
152
  Returns
143
153
  -------
144
154
  bool
145
- True if the string is JSON-dumped, False otherwise.
155
+ True if the value is JSON-dumped, False otherwise.
146
156
  """
157
+ to_check = value.strip() if isinstance(value, str) else value
147
158
  try:
148
- parsed = json.loads(value)
149
- # If we can parse it as JSON and it's not a string,
150
- # we consider it JSON-dumped
151
- return not isinstance(parsed, str)
159
+ parsed = json.loads(to_check)
160
+ return True, parsed
152
161
  except json.JSONDecodeError:
153
- return False
162
+ return False, value
154
163
 
155
164
 
156
165
  def try_parse_maybe_serialized(value: str) -> Any:
waldiez/io/ws.py CHANGED
@@ -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=too-many-try-statements
5
+ # pyright: reportMissingTypeStubs=false,reportReturnType=false
6
+
4
7
  """WebSocket IOStream implementation for AsyncIO."""
5
8
 
6
9
  import asyncio
@@ -30,12 +33,13 @@ from .utils import (
30
33
  LOG = logging.getLogger(__name__)
31
34
 
32
35
  if not is_websocket_available(): # pragma: no cover
33
- raise ImportError(
36
+ _msg = ( # pylint: disable=invalid-name
34
37
  "WebSocket support requires "
35
38
  "either the 'websockets' or 'starlette' package. "
36
39
  "Please install one of them with "
37
40
  "`pip install websockets` or `pip install starlette`."
38
41
  )
42
+ raise ImportError(_msg)
39
43
 
40
44
 
41
45
  class AsyncWebsocketsIOStream(IOStream):
@@ -118,8 +122,8 @@ class AsyncWebsocketsIOStream(IOStream):
118
122
  end = kwargs.get("end", "\n")
119
123
  msg = sep.join(str(arg) for arg in args)
120
124
 
121
- is_dumped = is_json_dumped(msg)
122
- if is_dumped and end.endswith("\n"): # pragma: no cover
125
+ is_dumped, msg = is_json_dumped(msg)
126
+ if is_dumped: # pragma: no cover
123
127
  msg = json.loads(msg)
124
128
  else:
125
129
  msg = f"{msg}{end}"
waldiez/logger.py CHANGED
@@ -3,6 +3,8 @@
3
3
  # pylint: disable=broad-exception-caught,too-many-try-statements
4
4
  """Waldiez logger."""
5
5
 
6
+ from __future__ import annotations
7
+
6
8
  import inspect
7
9
  import logging
8
10
  import os
@@ -10,13 +12,15 @@ import re
10
12
  import string
11
13
  import threading
12
14
  import traceback
15
+ from collections.abc import Mapping
13
16
  from datetime import datetime
14
17
  from enum import IntEnum
15
18
  from pathlib import Path
16
19
  from types import TracebackType
17
- from typing import Any, Callable, Mapping, Optional
20
+ from typing import Any, Callable
18
21
 
19
22
  import click
23
+ from typing_extensions import override
20
24
 
21
25
  HERE = Path(__file__).parent
22
26
 
@@ -41,7 +45,7 @@ class WaldiezLogger(logging.Logger):
41
45
  - logger.info("User {user} has {count} items", user="john", count=5)
42
46
  """
43
47
 
44
- _instance: Optional["WaldiezLogger"] = None
48
+ _instance: WaldiezLogger | None = None
45
49
  _lock: threading.Lock = threading.Lock()
46
50
 
47
51
  _INTERNAL_METHODS = {
@@ -179,6 +183,7 @@ class WaldiezLogger(logging.Logger):
179
183
  return self._level_map.get(level.upper(), LogLevel.INFO)
180
184
 
181
185
  # pylint: disable=unused-argument
186
+ @override
182
187
  def log(
183
188
  self,
184
189
  level: int,
@@ -249,6 +254,7 @@ class WaldiezLogger(logging.Logger):
249
254
  level_int = self._get_level_number(level)
250
255
  self.log(level_int, msg, *args, **kwargs)
251
256
 
257
+ @override
252
258
  def debug(self, msg: Any, *args: Any, **kwargs: Any) -> None:
253
259
  """Log a debug message.
254
260
 
@@ -263,6 +269,7 @@ class WaldiezLogger(logging.Logger):
263
269
  """
264
270
  self.do_log(msg, *args, level="debug", **kwargs)
265
271
 
272
+ @override
266
273
  def info(self, msg: Any, *args: Any, **kwargs: Any) -> None:
267
274
  """Log an informational message.
268
275
 
@@ -291,6 +298,7 @@ class WaldiezLogger(logging.Logger):
291
298
  """
292
299
  self.do_log(msg, *args, level="success", **kwargs)
293
300
 
301
+ @override
294
302
  def warning(self, msg: Any, *args: Any, **kwargs: Any) -> None:
295
303
  """Log a warning message.
296
304
 
@@ -305,6 +313,7 @@ class WaldiezLogger(logging.Logger):
305
313
  """
306
314
  self.do_log(msg, *args, level="warning", **kwargs)
307
315
 
316
+ @override
308
317
  def error(self, msg: Any, *args: Any, **kwargs: Any) -> None:
309
318
  """Log an error message.
310
319
 
@@ -319,6 +328,7 @@ class WaldiezLogger(logging.Logger):
319
328
  """
320
329
  self.do_log(msg, *args, level="error", **kwargs)
321
330
 
331
+ @override
322
332
  def critical(self, msg: Any, *args: Any, **kwargs: Any) -> None:
323
333
  """Log a critical error message.
324
334
 
@@ -333,6 +343,7 @@ class WaldiezLogger(logging.Logger):
333
343
  """
334
344
  self.do_log(msg, *args, level="critical", **kwargs)
335
345
 
346
+ @override
336
347
  def exception(self, msg: Any, *args: Any, **kwargs: Any) -> None:
337
348
  """Log an exception message.
338
349
 
@@ -354,6 +365,7 @@ class WaldiezLogger(logging.Logger):
354
365
  if tb and "NoneType: None" not in tb: # pragma: no branch
355
366
  click.echo(click.style(tb, fg="red", dim=True))
356
367
 
368
+ @override
357
369
  def setLevel(self, level: int | str) -> None:
358
370
  """
359
371
  Set the logging level.
@@ -388,10 +400,11 @@ class WaldiezLogger(logging.Logger):
388
400
  self._level = level_upper
389
401
  super().setLevel(self._level_map[level_upper].value)
390
402
  else:
391
- raise ValueError(
403
+ msg = (
392
404
  f"Invalid log level: {level}. "
393
405
  f"Valid levels are: {list(self._level_map.keys())}"
394
406
  )
407
+ raise ValueError(msg)
395
408
 
396
409
  def get_level(self) -> str:
397
410
  """Get the current logging level.
@@ -1,5 +1,8 @@
1
1
  # SPDX-License-Identifier: Apache-2.0.
2
2
  # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
+
4
+ # pyright: reportImportCycles=false
5
+
3
6
  """Agent models."""
4
7
 
5
8
  from .agent import (