waldiez 0.5.8__py3-none-any.whl → 0.5.10__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.
- waldiez/_version.py +1 -1
- waldiez/cli.py +112 -24
- waldiez/exporting/agent/exporter.py +3 -0
- waldiez/exporting/agent/extras/captain_agent_extras.py +44 -7
- waldiez/exporting/agent/extras/handoffs/condition.py +3 -1
- waldiez/exporting/chats/utils/common.py +25 -23
- waldiez/exporting/core/__init__.py +0 -2
- waldiez/exporting/core/context.py +13 -13
- waldiez/exporting/core/protocols.py +0 -141
- waldiez/exporting/core/result.py +5 -5
- waldiez/exporting/flow/merger.py +2 -2
- waldiez/exporting/flow/orchestrator.py +1 -0
- waldiez/exporting/flow/utils/common.py +2 -2
- waldiez/exporting/flow/utils/importing.py +1 -0
- waldiez/exporting/flow/utils/logging.py +6 -7
- waldiez/exporting/tools/exporter.py +5 -0
- waldiez/exporting/tools/factory.py +4 -0
- waldiez/exporting/tools/processor.py +5 -1
- waldiez/io/_ws.py +13 -5
- waldiez/io/models/content/image.py +1 -0
- waldiez/io/models/user_input.py +4 -4
- waldiez/io/models/user_response.py +1 -0
- waldiez/io/mqtt.py +1 -1
- waldiez/io/structured.py +17 -17
- waldiez/io/utils.py +1 -1
- waldiez/io/ws.py +9 -11
- waldiez/logger.py +180 -63
- waldiez/models/agents/agent/update_system_message.py +0 -2
- waldiez/models/agents/doc_agent/doc_agent.py +8 -1
- waldiez/models/common/dict_utils.py +169 -40
- waldiez/models/flow/flow.py +6 -6
- waldiez/models/flow/info.py +5 -1
- waldiez/models/model/_llm.py +28 -14
- waldiez/models/model/model.py +4 -1
- waldiez/models/model/model_data.py +18 -5
- waldiez/models/tool/predefined/_config.py +5 -1
- waldiez/models/tool/predefined/_duckduckgo.py +4 -0
- waldiez/models/tool/predefined/_email.py +474 -0
- waldiez/models/tool/predefined/_google.py +8 -6
- waldiez/models/tool/predefined/_perplexity.py +3 -0
- waldiez/models/tool/predefined/_searxng.py +3 -0
- waldiez/models/tool/predefined/_tavily.py +4 -1
- waldiez/models/tool/predefined/_wikipedia.py +4 -1
- waldiez/models/tool/predefined/_youtube.py +4 -1
- waldiez/models/tool/predefined/protocol.py +3 -0
- waldiez/models/tool/tool.py +22 -4
- waldiez/models/waldiez.py +12 -0
- waldiez/runner.py +37 -54
- waldiez/running/__init__.py +6 -0
- waldiez/running/base_runner.py +310 -353
- waldiez/running/environment.py +1 -0
- waldiez/running/exceptions.py +9 -0
- waldiez/running/post_run.py +4 -4
- waldiez/running/pre_run.py +51 -40
- waldiez/running/protocol.py +21 -101
- waldiez/running/run_results.py +1 -1
- waldiez/running/standard_runner.py +84 -277
- waldiez/running/step_by_step/__init__.py +46 -0
- waldiez/running/step_by_step/breakpoints_mixin.py +188 -0
- waldiez/running/step_by_step/step_by_step_models.py +224 -0
- waldiez/running/step_by_step/step_by_step_runner.py +745 -0
- waldiez/running/subprocess_runner/__base__.py +282 -0
- waldiez/running/subprocess_runner/__init__.py +16 -0
- waldiez/running/subprocess_runner/_async_runner.py +362 -0
- waldiez/running/subprocess_runner/_sync_runner.py +455 -0
- waldiez/running/subprocess_runner/runner.py +561 -0
- waldiez/running/timeline_processor.py +1 -1
- waldiez/running/utils.py +376 -1
- waldiez/utils/version.py +2 -6
- waldiez/ws/__init__.py +70 -0
- waldiez/ws/__main__.py +15 -0
- waldiez/ws/_file_handler.py +201 -0
- waldiez/ws/cli.py +211 -0
- waldiez/ws/client_manager.py +835 -0
- waldiez/ws/errors.py +416 -0
- waldiez/ws/models.py +971 -0
- waldiez/ws/reloader.py +342 -0
- waldiez/ws/server.py +469 -0
- waldiez/ws/session_manager.py +393 -0
- waldiez/ws/session_stats.py +83 -0
- waldiez/ws/utils.py +385 -0
- {waldiez-0.5.8.dist-info → waldiez-0.5.10.dist-info}/METADATA +74 -74
- {waldiez-0.5.8.dist-info → waldiez-0.5.10.dist-info}/RECORD +87 -65
- waldiez/running/patch_io_stream.py +0 -210
- {waldiez-0.5.8.dist-info → waldiez-0.5.10.dist-info}/WHEEL +0 -0
- {waldiez-0.5.8.dist-info → waldiez-0.5.10.dist-info}/entry_points.txt +0 -0
- {waldiez-0.5.8.dist-info → waldiez-0.5.10.dist-info}/licenses/LICENSE +0 -0
- {waldiez-0.5.8.dist-info → waldiez-0.5.10.dist-info}/licenses/NOTICE.md +0 -0
waldiez/exporting/core/result.py
CHANGED
|
@@ -59,7 +59,7 @@ class ExportResult:
|
|
|
59
59
|
position : ImportPosition, optional
|
|
60
60
|
The position of the import, by default THIRD_PARTY
|
|
61
61
|
"""
|
|
62
|
-
if statement and statement.strip():
|
|
62
|
+
if statement and statement.strip(): # pragma: no branch
|
|
63
63
|
self.imports.add(
|
|
64
64
|
ImportStatement(
|
|
65
65
|
statement=statement.strip(),
|
|
@@ -123,7 +123,7 @@ class ExportResult:
|
|
|
123
123
|
comment : Optional[str], optional
|
|
124
124
|
Optional comment for the argument, by default None
|
|
125
125
|
"""
|
|
126
|
-
if name and value is not None:
|
|
126
|
+
if name and value is not None: # pragma: no branch
|
|
127
127
|
arg = InstanceArgument(
|
|
128
128
|
instance_id=instance_id,
|
|
129
129
|
name=name,
|
|
@@ -173,7 +173,7 @@ class ExportResult:
|
|
|
173
173
|
position : ExportPosition, optional
|
|
174
174
|
The position for the merged content, by default AGENTS
|
|
175
175
|
"""
|
|
176
|
-
if other.main_content:
|
|
176
|
+
if other.main_content: # pragma: no branch
|
|
177
177
|
self.main_content = (
|
|
178
178
|
(self.main_content or "") + "\n" + other.main_content
|
|
179
179
|
).strip()
|
|
@@ -241,7 +241,7 @@ class ExportResult:
|
|
|
241
241
|
Additional metadata for the content, by default None
|
|
242
242
|
"""
|
|
243
243
|
order_value = order.value if isinstance(order, ContentOrder) else order
|
|
244
|
-
if content and content.strip():
|
|
244
|
+
if content and content.strip(): # pragma: no branch
|
|
245
245
|
positioned = PositionedContent(
|
|
246
246
|
content=content.strip() if not skip_strip else content,
|
|
247
247
|
position=position,
|
|
@@ -273,7 +273,7 @@ class ExportResult:
|
|
|
273
273
|
required : bool, optional
|
|
274
274
|
Whether the variable is required, by default True
|
|
275
275
|
"""
|
|
276
|
-
if name and value:
|
|
276
|
+
if name and value: # pragma: no branch
|
|
277
277
|
env_var = EnvironmentVariable(
|
|
278
278
|
name=name,
|
|
279
279
|
value=value,
|
waldiez/exporting/flow/merger.py
CHANGED
|
@@ -167,7 +167,7 @@ class ContentMerger:
|
|
|
167
167
|
# Log conflicts but continue
|
|
168
168
|
for conflict in conflicts:
|
|
169
169
|
self.context.get_logger().warning(
|
|
170
|
-
|
|
170
|
+
"Import conflict: %s", conflict
|
|
171
171
|
)
|
|
172
172
|
|
|
173
173
|
return set(grouped.values())
|
|
@@ -309,7 +309,7 @@ class ContentMerger:
|
|
|
309
309
|
# Log conflicts but continue (non-fatal)
|
|
310
310
|
for conflict in conflicts:
|
|
311
311
|
self.context.get_logger().warning(
|
|
312
|
-
|
|
312
|
+
"Environment variable conflict: %s", conflict
|
|
313
313
|
)
|
|
314
314
|
|
|
315
315
|
return list(env_vars.values())
|
|
@@ -113,7 +113,7 @@ def get_sync_sqlite_out() -> str:
|
|
|
113
113
|
cursor = conn.execute(query)
|
|
114
114
|
rows = cursor.fetchall()
|
|
115
115
|
column_names = [description[0] for description in cursor.description]
|
|
116
|
-
data = [dict(zip(column_names, row)) for row in rows]
|
|
116
|
+
data = [dict(zip(column_names, row, strict=True)) for row in rows]
|
|
117
117
|
conn.close()
|
|
118
118
|
with open(csv_file, "w", newline="", encoding="utf-8") as file:
|
|
119
119
|
csv_writer = csv.DictWriter(file, fieldnames=column_names)
|
|
@@ -148,7 +148,8 @@ def get_sync_sqlite_out() -> str:
|
|
|
148
148
|
content += " rows = cursor.fetchall()\n"
|
|
149
149
|
content += " column_names = [description[0] for description "
|
|
150
150
|
content += "in cursor.description]\n"
|
|
151
|
-
|
|
151
|
+
# pylint: disable=line-too-long
|
|
152
|
+
content += " data = [dict(zip(column_names, row, strict=True)) for row in rows]\n"
|
|
152
153
|
content += " conn.close()\n"
|
|
153
154
|
content += (
|
|
154
155
|
' with open(csv_file, "w", newline="", encoding="utf-8") as file:\n'
|
|
@@ -200,7 +201,7 @@ def get_async_sqlite_out() -> str:
|
|
|
200
201
|
return
|
|
201
202
|
rows = await cursor.fetchall()
|
|
202
203
|
column_names = [description[0] for description in cursor.description]
|
|
203
|
-
data = [dict(zip(column_names, row)) for row in rows]
|
|
204
|
+
data = [dict(zip(column_names, row, strict=True)) for row in rows]
|
|
204
205
|
await cursor.close()
|
|
205
206
|
await conn.close()
|
|
206
207
|
async with aiofiles.open(csv_file, "w", newline="", encoding="utf-8") as file:
|
|
@@ -235,7 +236,7 @@ def get_async_sqlite_out() -> str:
|
|
|
235
236
|
content += " rows = await cursor.fetchall()\n"
|
|
236
237
|
content += " column_names = [description[0] for description "
|
|
237
238
|
content += "in cursor.description]\n"
|
|
238
|
-
content += " data = [dict(zip(column_names, row)) for row in rows]\n"
|
|
239
|
+
content += " data = [dict(zip(column_names, row, strict=True)) for row in rows]\n"
|
|
239
240
|
content += " await cursor.close()\n"
|
|
240
241
|
content += " await conn.close()\n"
|
|
241
242
|
content += ' async with aiofiles.open(csv_file, "w", newline="", encoding="utf-8") as file:\n'
|
|
@@ -369,9 +370,7 @@ def get_stop_logging(is_async: bool, tabs: int = 0) -> str:
|
|
|
369
370
|
content += "def stop_logging() -> None:\n"
|
|
370
371
|
content += ' """Stop logging."""\n'
|
|
371
372
|
if is_async:
|
|
372
|
-
content += f"{tab}
|
|
373
|
-
content += f"{tab} from asyncer import asyncify\n\n"
|
|
374
|
-
content += f"{tab} await asyncify(runtime_logging.stop)()\n"
|
|
373
|
+
content += f"{tab} await asyncio.to_thread(runtime_logging.stop)\n"
|
|
375
374
|
else:
|
|
376
375
|
content += f"{tab} runtime_logging.stop()\n"
|
|
377
376
|
content += get_sqlite_out_call(tabs + 1, is_async)
|
|
@@ -32,6 +32,7 @@ class ToolsExporter(Exporter[ToolExtras]):
|
|
|
32
32
|
agent_names: dict[str, str],
|
|
33
33
|
tools: list[WaldiezTool],
|
|
34
34
|
tool_names: dict[str, str],
|
|
35
|
+
is_async: bool,
|
|
35
36
|
output_dir: str | Path | None = None,
|
|
36
37
|
context: ExporterContext | None = None,
|
|
37
38
|
**kwargs: Any,
|
|
@@ -50,6 +51,8 @@ class ToolsExporter(Exporter[ToolExtras]):
|
|
|
50
51
|
The tools to export.
|
|
51
52
|
tool_names : dict[str, str]
|
|
52
53
|
Mapping of tool IDs to names.
|
|
54
|
+
is_async : bool
|
|
55
|
+
Whether the flow is asynchronous.
|
|
53
56
|
output_dir : str | Path | None
|
|
54
57
|
Output directory for generated files, by default None
|
|
55
58
|
context : ExporterContext | None
|
|
@@ -64,6 +67,7 @@ class ToolsExporter(Exporter[ToolExtras]):
|
|
|
64
67
|
self.agent_names = agent_names
|
|
65
68
|
self.tools = tools
|
|
66
69
|
self.tool_names = tool_names
|
|
70
|
+
self.is_async = is_async
|
|
67
71
|
self.output_dir = Path(output_dir) if output_dir else None
|
|
68
72
|
|
|
69
73
|
# Initialize extras with processed tool content
|
|
@@ -83,6 +87,7 @@ class ToolsExporter(Exporter[ToolExtras]):
|
|
|
83
87
|
flow_name=self.flow_name,
|
|
84
88
|
tools=self.tools,
|
|
85
89
|
tool_names=self.tool_names,
|
|
90
|
+
is_async=self.is_async,
|
|
86
91
|
output_dir=self.output_dir,
|
|
87
92
|
)
|
|
88
93
|
|
|
@@ -16,6 +16,7 @@ def create_tools_exporter(
|
|
|
16
16
|
agent_names: dict[str, str],
|
|
17
17
|
tools: list[WaldiezTool],
|
|
18
18
|
tool_names: dict[str, str],
|
|
19
|
+
is_async: bool,
|
|
19
20
|
output_dir: str | Path | None = None,
|
|
20
21
|
context: ExporterContext | None = None,
|
|
21
22
|
) -> ToolsExporter:
|
|
@@ -33,6 +34,8 @@ def create_tools_exporter(
|
|
|
33
34
|
The tools to export.
|
|
34
35
|
tool_names : dict[str, str]
|
|
35
36
|
Mapping of tool IDs to names.
|
|
37
|
+
is_async : bool
|
|
38
|
+
Whether the flow is asynchronous.
|
|
36
39
|
output_dir : str | Path | None, optional
|
|
37
40
|
Output directory for generated files, by default None
|
|
38
41
|
context : ExporterContext | None, optional
|
|
@@ -52,5 +55,6 @@ def create_tools_exporter(
|
|
|
52
55
|
tools=tools,
|
|
53
56
|
tool_names=tool_names,
|
|
54
57
|
output_dir=output_dir,
|
|
58
|
+
is_async=is_async,
|
|
55
59
|
context=context,
|
|
56
60
|
)
|
|
@@ -33,6 +33,7 @@ class ToolProcessor:
|
|
|
33
33
|
flow_name: str,
|
|
34
34
|
tools: list[WaldiezTool],
|
|
35
35
|
tool_names: dict[str, str],
|
|
36
|
+
is_async: bool,
|
|
36
37
|
output_dir: Path | None = None,
|
|
37
38
|
):
|
|
38
39
|
"""Initialize the tool processor.
|
|
@@ -52,6 +53,7 @@ class ToolProcessor:
|
|
|
52
53
|
self.tools = tools
|
|
53
54
|
self.tool_names = tool_names
|
|
54
55
|
self.output_dir = output_dir
|
|
56
|
+
self.is_async = is_async
|
|
55
57
|
|
|
56
58
|
def process(self) -> ToolProcessingResult:
|
|
57
59
|
"""Process all tools and return consolidated result.
|
|
@@ -94,7 +96,9 @@ class ToolProcessor:
|
|
|
94
96
|
The result to add processed content to.
|
|
95
97
|
"""
|
|
96
98
|
# Get tool content
|
|
97
|
-
tool_content = tool.get_content(
|
|
99
|
+
tool_content = tool.get_content(
|
|
100
|
+
runtime_kwargs={"is_async": self.is_async}
|
|
101
|
+
)
|
|
98
102
|
if tool_content: # pragma: no branch
|
|
99
103
|
# Add interop conversion if needed
|
|
100
104
|
if tool.is_interop:
|
waldiez/io/_ws.py
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
"""WebSocket IOStream implementation for AsyncIO."""
|
|
6
6
|
|
|
7
7
|
import asyncio
|
|
8
|
+
import logging
|
|
8
9
|
from typing import Any, Protocol
|
|
9
10
|
|
|
10
11
|
HAS_WS_LIB = False
|
|
@@ -13,7 +14,7 @@ try:
|
|
|
13
14
|
from starlette.websockets import WebSocket # type: ignore[unused-ignore, unused-import, import-not-found, import-untyped] # noqa
|
|
14
15
|
|
|
15
16
|
HAS_WS_LIB = True # pyright: ignore
|
|
16
|
-
except ImportError:
|
|
17
|
+
except ImportError: # pragma: no cover
|
|
17
18
|
pass
|
|
18
19
|
|
|
19
20
|
try:
|
|
@@ -68,6 +69,7 @@ class WebSocketsAdapter:
|
|
|
68
69
|
The websockets library connection.
|
|
69
70
|
"""
|
|
70
71
|
self.websocket = websocket
|
|
72
|
+
self.log = logging.getLogger(__name__)
|
|
71
73
|
|
|
72
74
|
async def send_message(self, message: str) -> None:
|
|
73
75
|
"""Send a message using websockets library.
|
|
@@ -100,11 +102,14 @@ class WebSocketsAdapter:
|
|
|
100
102
|
)
|
|
101
103
|
if isinstance(response, bytes):
|
|
102
104
|
return response.decode("utf-8")
|
|
105
|
+
# noinspection PyUnreachableCode
|
|
103
106
|
return response if isinstance(response, str) else str(response)
|
|
104
107
|
except asyncio.TimeoutError:
|
|
105
108
|
return ""
|
|
106
|
-
except BaseException: # pylint: disable=broad-exception-caught
|
|
107
|
-
|
|
109
|
+
except BaseException as exc: # pylint: disable=broad-exception-caught
|
|
110
|
+
self.log.error(
|
|
111
|
+
"WebsocketsAdapter: Error receiving message: %s", exc
|
|
112
|
+
) # pragma: no cover
|
|
108
113
|
return ""
|
|
109
114
|
|
|
110
115
|
|
|
@@ -120,6 +125,7 @@ class StarletteAdapter:
|
|
|
120
125
|
The Starlette/FastAPI WebSocket connection.
|
|
121
126
|
"""
|
|
122
127
|
self.websocket = websocket
|
|
128
|
+
self.log = logging.getLogger(__name__)
|
|
123
129
|
|
|
124
130
|
async def send_message(self, message: str) -> None:
|
|
125
131
|
"""Send a message using Starlette WebSocket.
|
|
@@ -151,8 +157,10 @@ class StarletteAdapter:
|
|
|
151
157
|
)
|
|
152
158
|
except asyncio.TimeoutError:
|
|
153
159
|
return ""
|
|
154
|
-
except BaseException: # pylint: disable=broad-exception-caught
|
|
155
|
-
|
|
160
|
+
except BaseException as exc: # pylint: disable=broad-exception-caught
|
|
161
|
+
self.log.error(
|
|
162
|
+
"StarletteAdapter: Error receiving message: %s", exc
|
|
163
|
+
) # pragma: no cover
|
|
156
164
|
return ""
|
|
157
165
|
|
|
158
166
|
|
waldiez/io/models/user_input.py
CHANGED
|
@@ -97,7 +97,7 @@ class UserInputData(BaseModel):
|
|
|
97
97
|
parsed = json.loads(parsed)
|
|
98
98
|
except json.JSONDecodeError:
|
|
99
99
|
return TextMediaContent(type="text", text=parsed)
|
|
100
|
-
if isinstance(parsed, str):
|
|
100
|
+
if isinstance(parsed, str): # pragma: no cover
|
|
101
101
|
return TextMediaContent(type="text", text=parsed)
|
|
102
102
|
return cls.validate_content(parsed)
|
|
103
103
|
|
|
@@ -256,15 +256,15 @@ def extract_filename_from_path(path_or_url: str) -> str:
|
|
|
256
256
|
filename = os.path.basename(parsed.path)
|
|
257
257
|
|
|
258
258
|
# Handle edge cases where path might be empty or end with /
|
|
259
|
-
if not filename and parsed.path.endswith("/"):
|
|
259
|
+
if not filename and parsed.path.endswith("/"): # pragma: no cover
|
|
260
260
|
# Try to get the last directory name
|
|
261
261
|
path_parts = [p for p in parsed.path.split("/") if p]
|
|
262
262
|
filename = path_parts[-1] if path_parts else parsed.netloc
|
|
263
|
-
elif not filename:
|
|
263
|
+
elif not filename: # pragma: no cover
|
|
264
264
|
# Fallback to using netloc (domain) if no path
|
|
265
265
|
filename = parsed.netloc
|
|
266
266
|
|
|
267
267
|
return filename
|
|
268
268
|
# else:
|
|
269
269
|
# Local file path
|
|
270
|
-
return os.path.basename(path_or_url)
|
|
270
|
+
return os.path.basename(path_or_url) # pragma: no cover
|
|
@@ -209,6 +209,7 @@ class UserResponse(StructuredBase):
|
|
|
209
209
|
# we have probably returned sth till here
|
|
210
210
|
if isinstance(self.data, str): # pyright: ignore # pragma: no cover
|
|
211
211
|
return self.data
|
|
212
|
+
# noinspection PyUnreachableCode
|
|
212
213
|
return ( # pragma: no cover
|
|
213
214
|
json.dumps(self.data)
|
|
214
215
|
if hasattr(self.data, "__dict__")
|
waldiez/io/mqtt.py
CHANGED
|
@@ -473,7 +473,7 @@ class MqttIOStream(IOStream):
|
|
|
473
473
|
print_message = PrintMessage.create(*args, **kwargs)
|
|
474
474
|
try:
|
|
475
475
|
payload = print_message.model_dump(mode="json")
|
|
476
|
-
except Exception:
|
|
476
|
+
except Exception: # pragma: no cover
|
|
477
477
|
payload = print_message.model_dump(
|
|
478
478
|
serialize_as_any=True, mode="json", fallback=str
|
|
479
479
|
)
|
waldiez/io/structured.py
CHANGED
|
@@ -104,7 +104,7 @@ class StructuredIOStream(IOStream):
|
|
|
104
104
|
"""
|
|
105
105
|
request_id = uuid4().hex
|
|
106
106
|
prompt = prompt or ">"
|
|
107
|
-
if not prompt or prompt in [">", "> "]:
|
|
107
|
+
if not prompt or prompt in [">", "> "]: # pragma: no cover
|
|
108
108
|
# if the prompt is just ">" or "> ",
|
|
109
109
|
# let's use a more descriptive one
|
|
110
110
|
prompt = "Enter your message to start the conversation: "
|
|
@@ -116,22 +116,6 @@ class StructuredIOStream(IOStream):
|
|
|
116
116
|
uploads_root=self.uploads_root,
|
|
117
117
|
base_name=request_id,
|
|
118
118
|
)
|
|
119
|
-
# let's keep an eye here:
|
|
120
|
-
# https://github.com/ag2ai/ag2/blob/main/autogen/agentchat/conversable_agent.py#L2973
|
|
121
|
-
# reply = await iostream.input(prompt) ???? (await???)
|
|
122
|
-
if self.is_async:
|
|
123
|
-
# let's make a coroutine to just return the user response
|
|
124
|
-
async def async_input() -> str:
|
|
125
|
-
"""Asynchronous input method.
|
|
126
|
-
|
|
127
|
-
Returns
|
|
128
|
-
-------
|
|
129
|
-
str
|
|
130
|
-
The line read from the input stream.
|
|
131
|
-
"""
|
|
132
|
-
return user_response
|
|
133
|
-
|
|
134
|
-
return async_input() # type: ignore
|
|
135
119
|
return user_response
|
|
136
120
|
|
|
137
121
|
# noinspection PyMethodMayBeStatic
|
|
@@ -205,6 +189,11 @@ class StructuredIOStream(IOStream):
|
|
|
205
189
|
except queue.Empty:
|
|
206
190
|
self._send_timeout_message(request_id)
|
|
207
191
|
return ""
|
|
192
|
+
except BaseException as e:
|
|
193
|
+
self._send_error_message(request_id, str(e))
|
|
194
|
+
return ""
|
|
195
|
+
finally:
|
|
196
|
+
input_thread.join(timeout=1)
|
|
208
197
|
|
|
209
198
|
def _send_timeout_message(self, request_id: str) -> None:
|
|
210
199
|
timeout_payload = {
|
|
@@ -216,6 +205,17 @@ class StructuredIOStream(IOStream):
|
|
|
216
205
|
}
|
|
217
206
|
print(json.dumps(timeout_payload), flush=True, file=sys.stderr)
|
|
218
207
|
|
|
208
|
+
# noinspection PyMethodMayBeStatic
|
|
209
|
+
def _send_error_message(self, request_id: str, error_message: str) -> None:
|
|
210
|
+
error_payload = {
|
|
211
|
+
"id": gen_id(),
|
|
212
|
+
"type": "error",
|
|
213
|
+
"request_id": request_id,
|
|
214
|
+
"timestamp": now(),
|
|
215
|
+
"data": error_message,
|
|
216
|
+
}
|
|
217
|
+
print(json.dumps(error_payload), flush=True, file=sys.stderr)
|
|
218
|
+
|
|
219
219
|
def _handle_user_input(
|
|
220
220
|
self, user_input_raw: str, request_id: str
|
|
221
221
|
) -> UserResponse:
|
waldiez/io/utils.py
CHANGED
|
@@ -170,7 +170,7 @@ def try_parse_maybe_serialized(value: str) -> Any:
|
|
|
170
170
|
"""
|
|
171
171
|
for parser in (json.loads, ast.literal_eval):
|
|
172
172
|
# pylint: disable=broad-exception-caught, too-many-try-statements
|
|
173
|
-
# noinspection PyBroadException
|
|
173
|
+
# noinspection PyBroadException,TryExceptPass
|
|
174
174
|
try:
|
|
175
175
|
parsed: dict[str, Any] | list[Any] | str = parser(value)
|
|
176
176
|
# Normalize: if it's a single-item list of a string
|
waldiez/io/ws.py
CHANGED
|
@@ -29,7 +29,7 @@ from .utils import (
|
|
|
29
29
|
|
|
30
30
|
LOG = logging.getLogger(__name__)
|
|
31
31
|
|
|
32
|
-
if not is_websocket_available():
|
|
32
|
+
if not is_websocket_available(): # pragma: no cover
|
|
33
33
|
raise ImportError(
|
|
34
34
|
"WebSocket support requires "
|
|
35
35
|
"either the 'websockets' or 'starlette' package. "
|
|
@@ -72,14 +72,14 @@ class AsyncWebsocketsIOStream(IOStream):
|
|
|
72
72
|
websocket, "receive_message"
|
|
73
73
|
):
|
|
74
74
|
self.websocket: WebSocketConnection = websocket
|
|
75
|
-
else:
|
|
75
|
+
else: # pragma: no cover
|
|
76
76
|
self.websocket = create_websocket_adapter(websocket)
|
|
77
77
|
|
|
78
78
|
self.is_async = is_async
|
|
79
79
|
self.verbose = verbose
|
|
80
80
|
self.receive_timeout = receive_timeout
|
|
81
81
|
|
|
82
|
-
if isinstance(uploads_root, str):
|
|
82
|
+
if isinstance(uploads_root, str): # pragma: no cover
|
|
83
83
|
uploads_root = Path(uploads_root)
|
|
84
84
|
if uploads_root is not None:
|
|
85
85
|
uploads_root = uploads_root.resolve()
|
|
@@ -94,7 +94,7 @@ class AsyncWebsocketsIOStream(IOStream):
|
|
|
94
94
|
except RuntimeError:
|
|
95
95
|
pass
|
|
96
96
|
|
|
97
|
-
if loop is not None:
|
|
97
|
+
if loop is not None: # pragma: no cover
|
|
98
98
|
# We're in an async context, create a task
|
|
99
99
|
asyncio.create_task(self.websocket.send_message(json_dump))
|
|
100
100
|
else:
|
|
@@ -119,7 +119,7 @@ class AsyncWebsocketsIOStream(IOStream):
|
|
|
119
119
|
msg = sep.join(str(arg) for arg in args)
|
|
120
120
|
|
|
121
121
|
is_dumped = is_json_dumped(msg)
|
|
122
|
-
if is_dumped and end.endswith("\n"):
|
|
122
|
+
if is_dumped and end.endswith("\n"): # pragma: no cover
|
|
123
123
|
msg = json.loads(msg)
|
|
124
124
|
else:
|
|
125
125
|
msg = f"{msg}{end}"
|
|
@@ -145,7 +145,7 @@ class AsyncWebsocketsIOStream(IOStream):
|
|
|
145
145
|
"""
|
|
146
146
|
message_dump = get_message_dump(message)
|
|
147
147
|
|
|
148
|
-
if message_dump.get("type") == "text":
|
|
148
|
+
if message_dump.get("type") == "text": # pragma: no cover
|
|
149
149
|
content_block = message_dump.get("content")
|
|
150
150
|
if (
|
|
151
151
|
isinstance(content_block, dict)
|
|
@@ -186,7 +186,7 @@ class AsyncWebsocketsIOStream(IOStream):
|
|
|
186
186
|
|
|
187
187
|
try:
|
|
188
188
|
return asyncio.run(coro)
|
|
189
|
-
except RuntimeError:
|
|
189
|
+
except RuntimeError: # pragma: no cover
|
|
190
190
|
loop = asyncio.get_event_loop()
|
|
191
191
|
future = asyncio.run_coroutine_threadsafe(coro, loop)
|
|
192
192
|
return future.result()
|
|
@@ -215,7 +215,7 @@ class AsyncWebsocketsIOStream(IOStream):
|
|
|
215
215
|
str
|
|
216
216
|
The user input, or empty string if timeout exceeded.
|
|
217
217
|
"""
|
|
218
|
-
if timeout is None:
|
|
218
|
+
if timeout is None: # pragma: no branch
|
|
219
219
|
timeout = self.receive_timeout or 120.0
|
|
220
220
|
|
|
221
221
|
request_id = uuid.uuid4().hex
|
|
@@ -239,9 +239,7 @@ class AsyncWebsocketsIOStream(IOStream):
|
|
|
239
239
|
await self.websocket.send_message(prompt_dump)
|
|
240
240
|
response = await self.websocket.receive_message(timeout=timeout)
|
|
241
241
|
|
|
242
|
-
|
|
243
|
-
if not response:
|
|
244
|
-
LOG.warning("Input request timed out after %s seconds", timeout)
|
|
242
|
+
if not response: # pragma: no cover
|
|
245
243
|
return ""
|
|
246
244
|
|
|
247
245
|
if self.verbose:
|