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/ws/errors.py
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0.
|
|
2
|
+
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
|
+
# pylint: disable=unused-argument
|
|
4
|
+
"""Error handling and exceptions for Waldiez WebSocket server."""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from enum import IntEnum
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ErrorCode(IntEnum):
|
|
12
|
+
"""WebSocket error codes."""
|
|
13
|
+
|
|
14
|
+
# Standard WebSocket close codes
|
|
15
|
+
NORMAL_CLOSURE = 1000
|
|
16
|
+
GOING_AWAY = 1001
|
|
17
|
+
PROTOCOL_ERROR = 1002
|
|
18
|
+
UNSUPPORTED_DATA = 1003
|
|
19
|
+
INTERNAL_ERROR = 1011
|
|
20
|
+
TRY_AGAIN_LATER = 1013
|
|
21
|
+
|
|
22
|
+
# Custom application error codes (4000-4999 range)
|
|
23
|
+
INVALID_MESSAGE_FORMAT = 4000
|
|
24
|
+
UNSUPPORTED_ACTION = 4001
|
|
25
|
+
SERVER_OVERLOADED = 4002
|
|
26
|
+
INVALID_REQUEST_DATA = 4003
|
|
27
|
+
OPERATION_FAILED = 4004
|
|
28
|
+
NO_INPUT_REQUESTED = 4005
|
|
29
|
+
STALE_INPUT_REQUEST = 4006
|
|
30
|
+
SESSION_NOT_FOUND = 4007
|
|
31
|
+
TIMEOUT = 4008
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def string(self) -> str:
|
|
35
|
+
"""Get the string representation of the error code."""
|
|
36
|
+
# to upper camel case
|
|
37
|
+
return self.name.replace("_", " ").title().replace(" ", "")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class WaldiezServerError(Exception):
|
|
41
|
+
"""Base exception for Waldiez server errors."""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
message: str,
|
|
46
|
+
error_code: ErrorCode = ErrorCode.INTERNAL_ERROR,
|
|
47
|
+
details: dict[str, Any] | None = None,
|
|
48
|
+
):
|
|
49
|
+
"""Initialize server error.
|
|
50
|
+
|
|
51
|
+
Parameters
|
|
52
|
+
----------
|
|
53
|
+
message : str
|
|
54
|
+
Error message
|
|
55
|
+
error_code : ErrorCode
|
|
56
|
+
Error code
|
|
57
|
+
details : dict[str, Any] | None
|
|
58
|
+
Additional error details
|
|
59
|
+
"""
|
|
60
|
+
super().__init__(message)
|
|
61
|
+
self.message = message
|
|
62
|
+
self.error_code = error_code
|
|
63
|
+
self.details = details or {}
|
|
64
|
+
|
|
65
|
+
def to_dict(self) -> dict[str, Any]:
|
|
66
|
+
"""Convert error to dictionary.
|
|
67
|
+
|
|
68
|
+
Returns
|
|
69
|
+
-------
|
|
70
|
+
Dict[str, Any]
|
|
71
|
+
Error dictionary
|
|
72
|
+
"""
|
|
73
|
+
return {
|
|
74
|
+
"type": "error",
|
|
75
|
+
"message": self.message,
|
|
76
|
+
"code": int(self.error_code.value),
|
|
77
|
+
"error_type": self.error_code.string,
|
|
78
|
+
"details": self.details,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class MessageParsingError(WaldiezServerError):
|
|
83
|
+
"""Error parsing WebSocket message."""
|
|
84
|
+
|
|
85
|
+
def __init__(self, message: str, raw_data: str | None = None):
|
|
86
|
+
"""Initialize message parsing error.
|
|
87
|
+
|
|
88
|
+
Parameters
|
|
89
|
+
----------
|
|
90
|
+
message : str
|
|
91
|
+
Error message
|
|
92
|
+
raw_data : str | None
|
|
93
|
+
Raw message data that failed to parse
|
|
94
|
+
"""
|
|
95
|
+
details: dict[str, Any] = {}
|
|
96
|
+
if raw_data:
|
|
97
|
+
details["raw_data"] = raw_data[:500] # Limit size
|
|
98
|
+
|
|
99
|
+
super().__init__(
|
|
100
|
+
message,
|
|
101
|
+
ErrorCode.INVALID_MESSAGE_FORMAT,
|
|
102
|
+
details,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class UnsupportedActionError(WaldiezServerError):
|
|
107
|
+
"""Error for unsupported message actions."""
|
|
108
|
+
|
|
109
|
+
def __init__(self, action: str):
|
|
110
|
+
"""Initialize unsupported action error.
|
|
111
|
+
|
|
112
|
+
Parameters
|
|
113
|
+
----------
|
|
114
|
+
action : str
|
|
115
|
+
Unsupported action
|
|
116
|
+
"""
|
|
117
|
+
super().__init__(
|
|
118
|
+
f"Unsupported action: {action}",
|
|
119
|
+
ErrorCode.UNSUPPORTED_ACTION,
|
|
120
|
+
{"action": action},
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class ServerOverloadError(WaldiezServerError):
|
|
125
|
+
"""Error when server is overloaded."""
|
|
126
|
+
|
|
127
|
+
def __init__(self, current_clients: int, max_clients: int):
|
|
128
|
+
"""Initialize server overload error.
|
|
129
|
+
|
|
130
|
+
Parameters
|
|
131
|
+
----------
|
|
132
|
+
current_clients : int
|
|
133
|
+
Current number of clients
|
|
134
|
+
max_clients : int
|
|
135
|
+
Maximum allowed clients
|
|
136
|
+
"""
|
|
137
|
+
super().__init__(
|
|
138
|
+
f"Server overloaded: {current_clients}/{max_clients} clients",
|
|
139
|
+
ErrorCode.SERVER_OVERLOADED,
|
|
140
|
+
{
|
|
141
|
+
"current_clients": current_clients,
|
|
142
|
+
"max_clients": max_clients,
|
|
143
|
+
},
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class OperationTimeoutError(WaldiezServerError):
|
|
148
|
+
"""Error when operation times out."""
|
|
149
|
+
|
|
150
|
+
def __init__(self, operation: str, timeout: float):
|
|
151
|
+
"""Initialize operation timeout error.
|
|
152
|
+
|
|
153
|
+
Parameters
|
|
154
|
+
----------
|
|
155
|
+
operation : str
|
|
156
|
+
Operation that timed out
|
|
157
|
+
timeout : float
|
|
158
|
+
Timeout duration in seconds
|
|
159
|
+
"""
|
|
160
|
+
super().__init__(
|
|
161
|
+
f"Operation '{operation}' timed out after {timeout} seconds",
|
|
162
|
+
ErrorCode.TIMEOUT,
|
|
163
|
+
{
|
|
164
|
+
"operation": operation,
|
|
165
|
+
"timeout": timeout,
|
|
166
|
+
},
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class MessageHandlingError(WaldiezServerError):
|
|
171
|
+
"""Error during message processing/business logic."""
|
|
172
|
+
|
|
173
|
+
def __init__(
|
|
174
|
+
self,
|
|
175
|
+
operation: str,
|
|
176
|
+
details: str,
|
|
177
|
+
original_error: Exception | None = None,
|
|
178
|
+
):
|
|
179
|
+
"""Initialize message handling error.
|
|
180
|
+
|
|
181
|
+
Parameters
|
|
182
|
+
----------
|
|
183
|
+
operation : str
|
|
184
|
+
The operation that failed (e.g., "save", "run", "convert")
|
|
185
|
+
details : str
|
|
186
|
+
Details about the failure
|
|
187
|
+
original_error : Exception | None
|
|
188
|
+
The original exception that caused this error
|
|
189
|
+
"""
|
|
190
|
+
error_details = {"operation": operation, "details": details}
|
|
191
|
+
if original_error:
|
|
192
|
+
error_details["original_error"] = str(original_error)
|
|
193
|
+
|
|
194
|
+
super().__init__(
|
|
195
|
+
f"Failed to handle {operation}: {details}",
|
|
196
|
+
ErrorCode.OPERATION_FAILED,
|
|
197
|
+
error_details,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class SessionNotFoundError(WaldiezServerError):
|
|
202
|
+
"""Error when a session is not found."""
|
|
203
|
+
|
|
204
|
+
def __init__(self, session_id: str):
|
|
205
|
+
"""Initialize session not found error.
|
|
206
|
+
|
|
207
|
+
Parameters
|
|
208
|
+
----------
|
|
209
|
+
session_id : str
|
|
210
|
+
The ID of the session that was not found
|
|
211
|
+
"""
|
|
212
|
+
super().__init__(
|
|
213
|
+
f"Session not found: {session_id}",
|
|
214
|
+
ErrorCode.SESSION_NOT_FOUND,
|
|
215
|
+
{"session_id": session_id},
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class NoInputRequestedError(WaldiezServerError):
|
|
220
|
+
"""Error when no user input was requested."""
|
|
221
|
+
|
|
222
|
+
def __init__(self) -> None:
|
|
223
|
+
"""Initialize no input requested error."""
|
|
224
|
+
super().__init__(
|
|
225
|
+
"No user input requested",
|
|
226
|
+
ErrorCode.NO_INPUT_REQUESTED,
|
|
227
|
+
{},
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class StaleInputRequestError(WaldiezServerError):
|
|
232
|
+
"""Error when an input request is stale."""
|
|
233
|
+
|
|
234
|
+
def __init__(self, request_id: str, expected_id: str):
|
|
235
|
+
"""Initialize stale input request error.
|
|
236
|
+
|
|
237
|
+
Parameters
|
|
238
|
+
----------
|
|
239
|
+
request_id : str
|
|
240
|
+
The ID of the stale request
|
|
241
|
+
expected_id : str
|
|
242
|
+
The ID of the expected request
|
|
243
|
+
"""
|
|
244
|
+
super().__init__(
|
|
245
|
+
f"Stale input request. Expected {expected_id}, got {request_id}",
|
|
246
|
+
ErrorCode.STALE_INPUT_REQUEST,
|
|
247
|
+
{"request_id": request_id, "expected_id": expected_id},
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class ErrorHandler:
|
|
252
|
+
"""Centralized error handler for WebSocket server."""
|
|
253
|
+
|
|
254
|
+
def __init__(self, logger: logging.Logger | None = None):
|
|
255
|
+
"""Initialize error handler.
|
|
256
|
+
|
|
257
|
+
Parameters
|
|
258
|
+
----------
|
|
259
|
+
logger : logging.Logger | None
|
|
260
|
+
Logger instance
|
|
261
|
+
"""
|
|
262
|
+
self.logger = logger or logging.getLogger(__name__)
|
|
263
|
+
self.error_counts: dict[str, int] = {}
|
|
264
|
+
|
|
265
|
+
# noinspection PyUnusedLocal
|
|
266
|
+
def handle_error(
|
|
267
|
+
self,
|
|
268
|
+
error: Exception,
|
|
269
|
+
context: dict[str, Any] | None = None,
|
|
270
|
+
client_id: str | None = None,
|
|
271
|
+
) -> dict[str, Any]:
|
|
272
|
+
"""Handle an error and return appropriate response.
|
|
273
|
+
|
|
274
|
+
Parameters
|
|
275
|
+
----------
|
|
276
|
+
error : Exception
|
|
277
|
+
The error to handle
|
|
278
|
+
context : dict[str, Any] | None
|
|
279
|
+
Additional context information
|
|
280
|
+
client_id : str | None
|
|
281
|
+
Client ID if error is client-specific
|
|
282
|
+
|
|
283
|
+
Returns
|
|
284
|
+
-------
|
|
285
|
+
dict[str, Any]
|
|
286
|
+
Error response to send to client
|
|
287
|
+
"""
|
|
288
|
+
# Increment error count
|
|
289
|
+
error_type = type(error).__name__
|
|
290
|
+
self.error_counts[error_type] = self.error_counts.get(error_type, 0) + 1
|
|
291
|
+
|
|
292
|
+
# Handle known Waldiez errors
|
|
293
|
+
if isinstance(error, WaldiezServerError):
|
|
294
|
+
self.logger.warning(
|
|
295
|
+
"Waldiez error%s: %s",
|
|
296
|
+
f" (client: {client_id})" if client_id else "",
|
|
297
|
+
error.message,
|
|
298
|
+
)
|
|
299
|
+
return error.to_dict()
|
|
300
|
+
|
|
301
|
+
# Handle specific common errors
|
|
302
|
+
if isinstance(error, ValueError):
|
|
303
|
+
self.logger.warning(
|
|
304
|
+
"Validation error%s: %s",
|
|
305
|
+
f" (client: {client_id})" if client_id else "",
|
|
306
|
+
str(error),
|
|
307
|
+
)
|
|
308
|
+
return {
|
|
309
|
+
"type": "error",
|
|
310
|
+
"message": str(error),
|
|
311
|
+
"code": int(ErrorCode.INVALID_REQUEST_DATA),
|
|
312
|
+
"error_type": "ValidationError",
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if isinstance(error, TimeoutError):
|
|
316
|
+
self.logger.warning(
|
|
317
|
+
"Timeout error%s: %s",
|
|
318
|
+
f" (client: {client_id})" if client_id else "",
|
|
319
|
+
str(error),
|
|
320
|
+
)
|
|
321
|
+
return {
|
|
322
|
+
"type": "error",
|
|
323
|
+
"message": "Operation timed out",
|
|
324
|
+
"code": int(ErrorCode.TIMEOUT),
|
|
325
|
+
"error_type": "TimeoutError",
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
# Handle unexpected errors
|
|
329
|
+
self.logger.error(
|
|
330
|
+
"Unexpected error%s: %s",
|
|
331
|
+
f" (client: {client_id})" if client_id else "",
|
|
332
|
+
str(error),
|
|
333
|
+
exc_info=True,
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
# Return generic error response (don't expose internal details)
|
|
337
|
+
return {
|
|
338
|
+
"type": "error",
|
|
339
|
+
"message": "An internal server error occurred",
|
|
340
|
+
"code": int(ErrorCode.INTERNAL_ERROR),
|
|
341
|
+
"error_type": "InternalError",
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
def record_operational_error(
|
|
345
|
+
self, error_type: str, details: str | None = None
|
|
346
|
+
) -> None:
|
|
347
|
+
"""Record an operational error that isn't an exception.
|
|
348
|
+
|
|
349
|
+
Parameters
|
|
350
|
+
----------
|
|
351
|
+
error_type : str
|
|
352
|
+
Type of operational error
|
|
353
|
+
(e.g., "MessageSendFailure", "ConnectionDropped")
|
|
354
|
+
details : str | None
|
|
355
|
+
Optional details about the error
|
|
356
|
+
"""
|
|
357
|
+
self.error_counts[error_type] = self.error_counts.get(error_type, 0) + 1
|
|
358
|
+
|
|
359
|
+
# Log the operational error
|
|
360
|
+
if details:
|
|
361
|
+
self.logger.warning(
|
|
362
|
+
"Operational error [%s]: %s", error_type, details
|
|
363
|
+
)
|
|
364
|
+
else:
|
|
365
|
+
self.logger.warning("Operational error: %s", error_type)
|
|
366
|
+
|
|
367
|
+
def record_send_failure(self, client_id: str | None = None) -> None:
|
|
368
|
+
"""Handle message send failures.
|
|
369
|
+
|
|
370
|
+
Parameters
|
|
371
|
+
----------
|
|
372
|
+
client_id : str | None
|
|
373
|
+
Client ID if available
|
|
374
|
+
"""
|
|
375
|
+
details = f"client: {client_id}" if client_id else None
|
|
376
|
+
self.record_operational_error("MessageSendFailure", details)
|
|
377
|
+
|
|
378
|
+
def record_connection_drop(
|
|
379
|
+
self, client_id: str, reason: str | None = None
|
|
380
|
+
) -> None:
|
|
381
|
+
"""Handle connection drops.
|
|
382
|
+
|
|
383
|
+
Parameters
|
|
384
|
+
----------
|
|
385
|
+
client_id : str
|
|
386
|
+
Client ID
|
|
387
|
+
reason : str | None
|
|
388
|
+
Reason for connection drop
|
|
389
|
+
"""
|
|
390
|
+
details = f"client: {client_id}"
|
|
391
|
+
if reason:
|
|
392
|
+
details += f", reason: {reason}"
|
|
393
|
+
self.record_operational_error("ConnectionDropped", details)
|
|
394
|
+
|
|
395
|
+
def get_error_stats(self) -> dict[str, Any]:
|
|
396
|
+
"""Get error statistics.
|
|
397
|
+
|
|
398
|
+
Returns
|
|
399
|
+
-------
|
|
400
|
+
dict[str, Any]
|
|
401
|
+
Error statistics
|
|
402
|
+
"""
|
|
403
|
+
total_errors = sum(self.error_counts.values())
|
|
404
|
+
return {
|
|
405
|
+
"total_errors": total_errors,
|
|
406
|
+
"error_counts": self.error_counts.copy(),
|
|
407
|
+
"most_common_error": (
|
|
408
|
+
max(self.error_counts, key=lambda x: self.error_counts[x])
|
|
409
|
+
if self.error_counts
|
|
410
|
+
else None
|
|
411
|
+
),
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
def reset_stats(self) -> None:
|
|
415
|
+
"""Reset error statistics."""
|
|
416
|
+
self.error_counts.clear()
|