waldiez 0.4.6__py3-none-any.whl → 0.4.8__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 (244) hide show
  1. waldiez/__init__.py +5 -5
  2. waldiez/_version.py +1 -1
  3. waldiez/cli.py +112 -73
  4. waldiez/exporter.py +61 -19
  5. waldiez/exporting/__init__.py +25 -6
  6. waldiez/exporting/agent/__init__.py +7 -3
  7. waldiez/exporting/agent/code_execution.py +114 -0
  8. waldiez/exporting/agent/exporter.py +354 -0
  9. waldiez/exporting/agent/extras/__init__.py +15 -0
  10. waldiez/exporting/agent/extras/captain_agent_extras.py +315 -0
  11. waldiez/exporting/agent/extras/group/target.py +178 -0
  12. waldiez/exporting/agent/extras/group_manager_agent_extas.py +500 -0
  13. waldiez/exporting/agent/extras/group_member_extras.py +181 -0
  14. waldiez/exporting/agent/extras/handoffs/__init__.py +19 -0
  15. waldiez/exporting/agent/extras/handoffs/after_work.py +78 -0
  16. waldiez/exporting/agent/extras/handoffs/available.py +74 -0
  17. waldiez/exporting/agent/extras/handoffs/condition.py +158 -0
  18. waldiez/exporting/agent/extras/handoffs/handoff.py +171 -0
  19. waldiez/exporting/agent/extras/handoffs/target.py +189 -0
  20. waldiez/exporting/agent/extras/rag/__init__.py +10 -0
  21. waldiez/exporting/agent/{utils/rag_user/chroma_utils.py → extras/rag/chroma_extras.py} +16 -15
  22. waldiez/exporting/agent/{utils/rag_user/mongo_utils.py → extras/rag/mongo_extras.py} +10 -10
  23. waldiez/exporting/agent/{utils/rag_user/pgvector_utils.py → extras/rag/pgvector_extras.py} +13 -13
  24. waldiez/exporting/agent/{utils/rag_user/qdrant_utils.py → extras/rag/qdrant_extras.py} +13 -13
  25. waldiez/exporting/agent/{utils/rag_user/vector_db.py → extras/rag/vector_db_extras.py} +59 -46
  26. waldiez/exporting/agent/extras/rag_user_proxy_agent_extras.py +245 -0
  27. waldiez/exporting/agent/extras/reasoning_agent_extras.py +88 -0
  28. waldiez/exporting/agent/factory.py +95 -0
  29. waldiez/exporting/agent/processor.py +150 -0
  30. waldiez/exporting/agent/system_message.py +36 -0
  31. waldiez/exporting/agent/termination.py +50 -0
  32. waldiez/exporting/chats/__init__.py +7 -3
  33. waldiez/exporting/chats/exporter.py +97 -0
  34. waldiez/exporting/chats/factory.py +65 -0
  35. waldiez/exporting/chats/processor.py +226 -0
  36. waldiez/exporting/chats/utils/__init__.py +6 -5
  37. waldiez/exporting/chats/utils/common.py +11 -45
  38. waldiez/exporting/chats/utils/group.py +55 -0
  39. waldiez/exporting/chats/utils/nested.py +37 -52
  40. waldiez/exporting/chats/utils/sequential.py +72 -61
  41. waldiez/exporting/chats/utils/{single_chat.py → single.py} +48 -50
  42. waldiez/exporting/core/__init__.py +196 -0
  43. waldiez/exporting/core/constants.py +17 -0
  44. waldiez/exporting/core/content.py +69 -0
  45. waldiez/exporting/core/context.py +244 -0
  46. waldiez/exporting/core/enums.py +89 -0
  47. waldiez/exporting/core/errors.py +19 -0
  48. waldiez/exporting/core/exporter.py +390 -0
  49. waldiez/exporting/core/exporters.py +67 -0
  50. waldiez/exporting/core/extras/__init__.py +39 -0
  51. waldiez/exporting/core/extras/agent_extras/__init__.py +27 -0
  52. waldiez/exporting/core/extras/agent_extras/captain_extras.py +57 -0
  53. waldiez/exporting/core/extras/agent_extras/group_manager_extras.py +102 -0
  54. waldiez/exporting/core/extras/agent_extras/rag_user_extras.py +53 -0
  55. waldiez/exporting/core/extras/agent_extras/reasoning_extras.py +68 -0
  56. waldiez/exporting/core/extras/agent_extras/standard_extras.py +263 -0
  57. waldiez/exporting/core/extras/base.py +241 -0
  58. waldiez/exporting/core/extras/chat_extras.py +118 -0
  59. waldiez/exporting/core/extras/flow_extras.py +70 -0
  60. waldiez/exporting/core/extras/model_extras.py +73 -0
  61. waldiez/exporting/core/extras/path_resolver.py +93 -0
  62. waldiez/exporting/core/extras/serializer.py +138 -0
  63. waldiez/exporting/core/extras/tool_extras.py +82 -0
  64. waldiez/exporting/core/protocols.py +259 -0
  65. waldiez/exporting/core/result.py +705 -0
  66. waldiez/exporting/core/types.py +329 -0
  67. waldiez/exporting/core/utils/__init__.py +11 -0
  68. waldiez/exporting/core/utils/comment.py +33 -0
  69. waldiez/exporting/core/utils/llm_config.py +117 -0
  70. waldiez/exporting/core/validation.py +96 -0
  71. waldiez/exporting/flow/__init__.py +6 -2
  72. waldiez/exporting/flow/execution_generator.py +193 -0
  73. waldiez/exporting/flow/exporter.py +107 -0
  74. waldiez/exporting/flow/factory.py +94 -0
  75. waldiez/exporting/flow/file_generator.py +214 -0
  76. waldiez/exporting/flow/merger.py +387 -0
  77. waldiez/exporting/flow/orchestrator.py +411 -0
  78. waldiez/exporting/flow/utils/__init__.py +9 -36
  79. waldiez/exporting/flow/utils/common.py +206 -0
  80. waldiez/exporting/flow/utils/importing.py +373 -0
  81. waldiez/exporting/flow/utils/linting.py +200 -0
  82. waldiez/exporting/flow/utils/{logging_utils.py → logging.py} +23 -9
  83. waldiez/exporting/models/__init__.py +3 -1
  84. waldiez/exporting/models/exporter.py +233 -0
  85. waldiez/exporting/models/factory.py +66 -0
  86. waldiez/exporting/models/processor.py +139 -0
  87. waldiez/exporting/tools/__init__.py +11 -0
  88. waldiez/exporting/tools/exporter.py +207 -0
  89. waldiez/exporting/tools/factory.py +57 -0
  90. waldiez/exporting/tools/processor.py +248 -0
  91. waldiez/exporting/tools/registration.py +133 -0
  92. waldiez/io/__init__.py +128 -0
  93. waldiez/io/_ws.py +199 -0
  94. waldiez/io/models/__init__.py +60 -0
  95. waldiez/io/models/base.py +66 -0
  96. waldiez/io/models/constants.py +78 -0
  97. waldiez/io/models/content/__init__.py +23 -0
  98. waldiez/io/models/content/audio.py +43 -0
  99. waldiez/io/models/content/base.py +45 -0
  100. waldiez/io/models/content/file.py +43 -0
  101. waldiez/io/models/content/image.py +96 -0
  102. waldiez/io/models/content/text.py +37 -0
  103. waldiez/io/models/content/video.py +43 -0
  104. waldiez/io/models/user_input.py +269 -0
  105. waldiez/io/models/user_response.py +215 -0
  106. waldiez/io/mqtt.py +681 -0
  107. waldiez/io/redis.py +782 -0
  108. waldiez/io/structured.py +419 -0
  109. waldiez/io/utils.py +184 -0
  110. waldiez/io/ws.py +298 -0
  111. waldiez/logger.py +481 -0
  112. waldiez/models/__init__.py +108 -51
  113. waldiez/models/agents/__init__.py +34 -70
  114. waldiez/models/agents/agent/__init__.py +10 -4
  115. waldiez/models/agents/agent/agent.py +466 -65
  116. waldiez/models/agents/agent/agent_data.py +119 -47
  117. waldiez/models/agents/agent/agent_type.py +13 -2
  118. waldiez/models/agents/agent/code_execution.py +12 -12
  119. waldiez/models/agents/agent/human_input_mode.py +8 -0
  120. waldiez/models/agents/agent/{linked_skill.py → linked_tool.py} +7 -7
  121. waldiez/models/agents/agent/nested_chat.py +35 -7
  122. waldiez/models/agents/agent/termination_message.py +30 -22
  123. waldiez/models/agents/{swarm_agent → agent}/update_system_message.py +22 -22
  124. waldiez/models/agents/agents.py +58 -63
  125. waldiez/models/agents/assistant/assistant.py +4 -4
  126. waldiez/models/agents/assistant/assistant_data.py +13 -1
  127. waldiez/models/agents/{captain_agent → captain}/captain_agent.py +5 -5
  128. waldiez/models/agents/{captain_agent → captain}/captain_agent_data.py +5 -5
  129. waldiez/models/agents/extra_requirements.py +11 -16
  130. waldiez/models/agents/group_manager/group_manager.py +103 -13
  131. waldiez/models/agents/group_manager/group_manager_data.py +36 -14
  132. waldiez/models/agents/group_manager/speakers.py +77 -24
  133. waldiez/models/agents/{rag_user → rag_user_proxy}/__init__.py +16 -16
  134. waldiez/models/agents/rag_user_proxy/rag_user_proxy.py +64 -0
  135. waldiez/models/agents/{rag_user/rag_user_data.py → rag_user_proxy/rag_user_proxy_data.py} +6 -5
  136. waldiez/models/agents/{rag_user → rag_user_proxy}/retrieve_config.py +182 -114
  137. waldiez/models/agents/{rag_user → rag_user_proxy}/vector_db_config.py +13 -13
  138. waldiez/models/agents/reasoning/reasoning_agent.py +6 -6
  139. waldiez/models/agents/reasoning/reasoning_agent_data.py +110 -63
  140. waldiez/models/agents/reasoning/reasoning_agent_reason_config.py +38 -10
  141. waldiez/models/agents/user_proxy/user_proxy.py +11 -7
  142. waldiez/models/agents/user_proxy/user_proxy_data.py +2 -2
  143. waldiez/models/chat/__init__.py +2 -1
  144. waldiez/models/chat/chat.py +166 -87
  145. waldiez/models/chat/chat_data.py +99 -136
  146. waldiez/models/chat/chat_message.py +33 -23
  147. waldiez/models/chat/chat_nested.py +31 -30
  148. waldiez/models/chat/chat_summary.py +10 -8
  149. waldiez/models/common/__init__.py +52 -2
  150. waldiez/models/common/ag2_version.py +1 -1
  151. waldiez/models/common/base.py +38 -7
  152. waldiez/models/common/dict_utils.py +42 -17
  153. waldiez/models/common/handoff.py +459 -0
  154. waldiez/models/common/id_generator.py +19 -0
  155. waldiez/models/common/method_utils.py +130 -68
  156. waldiez/{exporting/base/utils → models/common}/naming.py +38 -61
  157. waldiez/models/common/waldiez_version.py +37 -0
  158. waldiez/models/flow/__init__.py +9 -2
  159. waldiez/models/flow/connection.py +18 -0
  160. waldiez/models/flow/flow.py +311 -215
  161. waldiez/models/flow/flow_data.py +207 -40
  162. waldiez/models/flow/info.py +85 -0
  163. waldiez/models/flow/naming.py +131 -0
  164. waldiez/models/model/__init__.py +7 -1
  165. waldiez/models/model/extra_requirements.py +3 -12
  166. waldiez/models/model/model.py +76 -21
  167. waldiez/models/model/model_data.py +108 -20
  168. waldiez/models/tool/__init__.py +16 -0
  169. waldiez/models/tool/extra_requirements.py +36 -0
  170. waldiez/models/{skill/skill.py → tool/tool.py} +88 -88
  171. waldiez/models/tool/tool_data.py +51 -0
  172. waldiez/models/tool/tool_type.py +8 -0
  173. waldiez/models/waldiez.py +97 -80
  174. waldiez/runner.py +114 -49
  175. waldiez/running/__init__.py +1 -1
  176. waldiez/running/environment.py +49 -68
  177. waldiez/running/gen_seq_diagram.py +16 -14
  178. waldiez/running/running.py +53 -34
  179. waldiez/utils/__init__.py +0 -4
  180. waldiez/utils/cli_extras/jupyter.py +5 -3
  181. waldiez/utils/cli_extras/runner.py +6 -4
  182. waldiez/utils/cli_extras/studio.py +6 -4
  183. waldiez/utils/conflict_checker.py +15 -9
  184. waldiez/utils/flaml_warnings.py +5 -5
  185. {waldiez-0.4.6.dist-info → waldiez-0.4.8.dist-info}/METADATA +235 -91
  186. waldiez-0.4.8.dist-info/RECORD +200 -0
  187. waldiez/exporting/agent/agent_exporter.py +0 -297
  188. waldiez/exporting/agent/utils/__init__.py +0 -23
  189. waldiez/exporting/agent/utils/captain_agent.py +0 -263
  190. waldiez/exporting/agent/utils/code_execution.py +0 -65
  191. waldiez/exporting/agent/utils/group_manager.py +0 -220
  192. waldiez/exporting/agent/utils/rag_user/__init__.py +0 -7
  193. waldiez/exporting/agent/utils/rag_user/rag_user.py +0 -209
  194. waldiez/exporting/agent/utils/reasoning.py +0 -36
  195. waldiez/exporting/agent/utils/swarm_agent.py +0 -469
  196. waldiez/exporting/agent/utils/teachability.py +0 -41
  197. waldiez/exporting/agent/utils/termination_message.py +0 -44
  198. waldiez/exporting/base/__init__.py +0 -25
  199. waldiez/exporting/base/agent_position.py +0 -75
  200. waldiez/exporting/base/base_exporter.py +0 -118
  201. waldiez/exporting/base/export_position.py +0 -48
  202. waldiez/exporting/base/import_position.py +0 -23
  203. waldiez/exporting/base/mixin.py +0 -137
  204. waldiez/exporting/base/utils/__init__.py +0 -18
  205. waldiez/exporting/base/utils/comments.py +0 -96
  206. waldiez/exporting/base/utils/path_check.py +0 -68
  207. waldiez/exporting/base/utils/to_string.py +0 -84
  208. waldiez/exporting/chats/chats_exporter.py +0 -240
  209. waldiez/exporting/chats/utils/swarm.py +0 -210
  210. waldiez/exporting/flow/flow_exporter.py +0 -528
  211. waldiez/exporting/flow/utils/agent_utils.py +0 -204
  212. waldiez/exporting/flow/utils/chat_utils.py +0 -71
  213. waldiez/exporting/flow/utils/def_main.py +0 -77
  214. waldiez/exporting/flow/utils/flow_content.py +0 -202
  215. waldiez/exporting/flow/utils/flow_names.py +0 -116
  216. waldiez/exporting/flow/utils/importing_utils.py +0 -227
  217. waldiez/exporting/models/models_exporter.py +0 -199
  218. waldiez/exporting/models/utils.py +0 -174
  219. waldiez/exporting/skills/__init__.py +0 -9
  220. waldiez/exporting/skills/skills_exporter.py +0 -176
  221. waldiez/exporting/skills/utils.py +0 -369
  222. waldiez/models/agents/agent/teachability.py +0 -70
  223. waldiez/models/agents/rag_user/rag_user.py +0 -60
  224. waldiez/models/agents/swarm_agent/__init__.py +0 -50
  225. waldiez/models/agents/swarm_agent/after_work.py +0 -179
  226. waldiez/models/agents/swarm_agent/on_condition.py +0 -105
  227. waldiez/models/agents/swarm_agent/on_condition_available.py +0 -142
  228. waldiez/models/agents/swarm_agent/on_condition_target.py +0 -40
  229. waldiez/models/agents/swarm_agent/swarm_agent.py +0 -107
  230. waldiez/models/agents/swarm_agent/swarm_agent_data.py +0 -124
  231. waldiez/models/flow/utils.py +0 -232
  232. waldiez/models/skill/__init__.py +0 -16
  233. waldiez/models/skill/extra_requirements.py +0 -36
  234. waldiez/models/skill/skill_data.py +0 -53
  235. waldiez/models/skill/skill_type.py +0 -8
  236. waldiez/utils/pysqlite3_checker.py +0 -308
  237. waldiez/utils/rdps_checker.py +0 -122
  238. waldiez-0.4.6.dist-info/RECORD +0 -149
  239. /waldiez/models/agents/{captain_agent → captain}/__init__.py +0 -0
  240. /waldiez/models/agents/{captain_agent → captain}/captain_agent_lib_entry.py +0 -0
  241. {waldiez-0.4.6.dist-info → waldiez-0.4.8.dist-info}/WHEEL +0 -0
  242. {waldiez-0.4.6.dist-info → waldiez-0.4.8.dist-info}/entry_points.txt +0 -0
  243. {waldiez-0.4.6.dist-info → waldiez-0.4.8.dist-info}/licenses/LICENSE +0 -0
  244. {waldiez-0.4.6.dist-info → waldiez-0.4.8.dist-info}/licenses/NOTICE.md +0 -0
waldiez/io/ws.py ADDED
@@ -0,0 +1,298 @@
1
+ # SPDX-License-Identifier: Apache-2.0.
2
+ # Copyright (c) 2024 - 2025 Waldiez and contributors.
3
+ # pylint: disable=too-many-try-statements
4
+ """WebSocket IOStream implementation for AsyncIO."""
5
+
6
+ import asyncio
7
+ import json
8
+ import logging
9
+ import uuid
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ from autogen.events import BaseEvent # type: ignore
14
+ from autogen.io import IOStream # type: ignore
15
+
16
+ from ._ws import (
17
+ WebSocketConnection,
18
+ create_websocket_adapter,
19
+ is_websocket_available,
20
+ )
21
+ from .models import UserResponse
22
+ from .utils import is_json_dumped, now, try_parse_maybe_serialized
23
+
24
+ LOG = logging.getLogger(__name__)
25
+
26
+ if not is_websocket_available():
27
+ raise ImportError(
28
+ "WebSocket support requires "
29
+ "either the 'websockets' or 'starlette' package. "
30
+ "Please install one of them with "
31
+ "`pip install websockets` or `pip install starlette`."
32
+ )
33
+
34
+
35
+ class AsyncWebsocketsIOStream(IOStream):
36
+ """AsyncIO WebSocket class to handle communication."""
37
+
38
+ def __init__(
39
+ self,
40
+ websocket: Any,
41
+ is_async: bool = False,
42
+ uploads_root: str | Path | None = None,
43
+ verbose: bool = False,
44
+ receive_timeout: float | None = 120.0,
45
+ ) -> None:
46
+ """Initialize the AsyncWebsocketsIOStream instance.
47
+
48
+ Parameters
49
+ ----------
50
+ websocket : Any
51
+ The WebSocket connection (either websockets or Starlette/FastAPI).
52
+ is_async : bool
53
+ Whether the connection is asynchronous.
54
+ uploads_root : str | Path | None
55
+ The root directory for uploads.
56
+ verbose : bool
57
+ Whether to enable verbose logging.
58
+ receive_timeout : float | None
59
+ Default timeout for receiving messages in seconds.
60
+ If None, defaults to 120 seconds.
61
+ """
62
+ super().__init__()
63
+
64
+ # Create the WebSocket adapter
65
+ if hasattr(websocket, "send_message") and hasattr(
66
+ websocket, "receive_message"
67
+ ):
68
+ self.websocket: WebSocketConnection = websocket
69
+ else:
70
+ self.websocket = create_websocket_adapter(websocket)
71
+
72
+ self.is_async = is_async
73
+ self.verbose = verbose
74
+ self.receive_timeout = receive_timeout
75
+
76
+ if isinstance(uploads_root, str):
77
+ uploads_root = Path(uploads_root)
78
+ if uploads_root is not None:
79
+ uploads_root = uploads_root.resolve()
80
+ self.uploads_root = uploads_root
81
+
82
+ def _try_send(self, json_dump: str) -> None:
83
+ try:
84
+ # Check if we're already in an async context
85
+ loop = None
86
+ try:
87
+ loop = asyncio.get_running_loop()
88
+ except RuntimeError:
89
+ pass
90
+
91
+ if loop is not None:
92
+ # We're in an async context, create a task
93
+ asyncio.create_task(self.websocket.send_message(json_dump))
94
+ else:
95
+ # We're not in an async context, run the coroutine
96
+ asyncio.run(self.websocket.send_message(json_dump))
97
+
98
+ except BaseException as error: # pylint: disable=broad-exception-caught
99
+ LOG.error("Error sending message: %s", error)
100
+
101
+ def print(self, *args: Any, **kwargs: Any) -> None:
102
+ """Print to the WebSocket connection.
103
+
104
+ Parameters
105
+ ----------
106
+ args : tuple
107
+ The arguments to print.
108
+ kwargs : dict
109
+ The keyword arguments to print.
110
+ """
111
+ sep = kwargs.get("sep", " ")
112
+ end = kwargs.get("end", "\n")
113
+ msg = sep.join(str(arg) for arg in args)
114
+
115
+ is_dumped = is_json_dumped(msg)
116
+ if is_dumped and end.endswith("\n"):
117
+ msg = json.loads(msg)
118
+ else:
119
+ msg = f"{msg}{end}"
120
+
121
+ json_dump = json.dumps(
122
+ {
123
+ "type": "print",
124
+ "data": msg,
125
+ }
126
+ )
127
+
128
+ if self.verbose:
129
+ LOG.info(json_dump)
130
+ self._try_send(json_dump)
131
+
132
+ def send(self, message: BaseEvent) -> None:
133
+ """Send a message to the WebSocket connection.
134
+
135
+ Parameters
136
+ ----------
137
+ message : BaseEvent
138
+ The message to send.
139
+ """
140
+ message_dump = message.model_dump(mode="json")
141
+
142
+ if message_dump.get("type") == "text":
143
+ content_block = message_dump.get("content")
144
+ if (
145
+ isinstance(content_block, dict)
146
+ and "content" in content_block
147
+ and isinstance(content_block["content"], str)
148
+ ):
149
+ inner_content = content_block["content"]
150
+ content_block["content"] = try_parse_maybe_serialized(
151
+ inner_content
152
+ )
153
+
154
+ json_dump = json.dumps(message_dump, ensure_ascii=False)
155
+
156
+ if self.verbose:
157
+ LOG.info("sending: \n%s\n", json_dump)
158
+
159
+ self._try_send(json_dump)
160
+
161
+ def input(self, prompt: str = "", *, password: bool = False) -> str:
162
+ """Sync-compatible input (will run the async version in the loop).
163
+
164
+ Parameters
165
+ ----------
166
+ prompt : str
167
+ The prompt to display.
168
+ password : bool
169
+ Whether to hide the input.
170
+
171
+ Returns
172
+ -------
173
+ str
174
+ The user input.
175
+ """
176
+ coro = self.a_input(prompt, password=password)
177
+ if self.is_async:
178
+ return coro # type: ignore # pragma: no cover
179
+
180
+ try:
181
+ return asyncio.run(coro)
182
+ except RuntimeError:
183
+ loop = asyncio.get_event_loop()
184
+ future = asyncio.run_coroutine_threadsafe(coro, loop)
185
+ return future.result()
186
+
187
+ async def a_input(
188
+ self,
189
+ prompt: str = "",
190
+ *,
191
+ password: bool = False,
192
+ timeout: float | None = None,
193
+ ) -> str:
194
+ """Get input from the WebSocket connection.
195
+
196
+ Parameters
197
+ ----------
198
+ prompt : str
199
+ The prompt to display.
200
+ password : bool
201
+ Whether to hide the input.
202
+ timeout : float | None
203
+ Timeout for receiving the response in seconds.
204
+ If None, uses the instance's default receive_timeout.
205
+
206
+ Returns
207
+ -------
208
+ str
209
+ The user input, or empty string if timeout exceeded.
210
+ """
211
+ if timeout is None:
212
+ timeout = self.receive_timeout or 120.0
213
+
214
+ request_id = uuid.uuid4().hex
215
+ prompt_dump = json.dumps(
216
+ {
217
+ "id": request_id,
218
+ "timestamp": now(),
219
+ "type": "input_request",
220
+ "request_id": request_id,
221
+ "prompt": prompt,
222
+ "password": password,
223
+ "content": [
224
+ {
225
+ "type": "text",
226
+ "text": prompt,
227
+ }
228
+ ],
229
+ }
230
+ )
231
+
232
+ await self.websocket.send_message(prompt_dump)
233
+ response = await self.websocket.receive_message(timeout=timeout)
234
+
235
+ # Handle empty response from timeout
236
+ if not response:
237
+ LOG.warning("Input request timed out after %s seconds", timeout)
238
+ return ""
239
+
240
+ if self.verbose:
241
+ LOG.info("Got input: %s ...", response[:300])
242
+
243
+ response_dict: dict[str, Any] | str
244
+ try:
245
+ response_dict = json.loads(response)
246
+ except json.JSONDecodeError:
247
+ return response
248
+
249
+ if not isinstance(response_dict, dict):
250
+ LOG.error("Invalid input response: %s", response)
251
+ return ""
252
+
253
+ return self._parse_response(response_dict, request_id)
254
+
255
+ def _parse_response(self, response: dict[str, Any], request_id: str) -> str:
256
+ """Parse the response from the WebSocket connection.
257
+
258
+ Parameters
259
+ ----------
260
+ response : dict
261
+ The response from the WebSocket connection.
262
+ request_id : str
263
+ The request ID of the input request.
264
+
265
+ Returns
266
+ -------
267
+ str
268
+ The parsed response content.
269
+ """
270
+ if "data" in response:
271
+ response_data = response["data"]
272
+ if isinstance(response_data, str): # pragma: no branch
273
+ try:
274
+ # double dumped?
275
+ parsed = json.loads(response_data)
276
+ except json.JSONDecodeError:
277
+ pass
278
+ else:
279
+ response["data"] = parsed
280
+
281
+ try:
282
+ user_response = UserResponse.model_validate(response)
283
+ except Exception as error: # pylint: disable=broad-exception-caught
284
+ LOG.error("Error parsing user input response: %s", error)
285
+ return ""
286
+
287
+ if user_response.request_id != request_id:
288
+ LOG.error(
289
+ "User response request ID mismatch: %s != %s",
290
+ user_response.request_id,
291
+ request_id,
292
+ )
293
+ return ""
294
+
295
+ return user_response.to_string(
296
+ uploads_root=self.uploads_root,
297
+ base_name=request_id,
298
+ )