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/reloader.py
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0.
|
|
2
|
+
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
|
+
"""Auto-reload functionality for development."""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import threading
|
|
9
|
+
import time
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from types import TracebackType
|
|
12
|
+
from typing import Any, Callable
|
|
13
|
+
|
|
14
|
+
from watchdog.events import FileSystemEvent, FileSystemEventHandler
|
|
15
|
+
from watchdog.observers import Observer
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ReloadHandler(FileSystemEventHandler):
|
|
21
|
+
"""Handler for file system events that triggers server reload."""
|
|
22
|
+
|
|
23
|
+
_cwd: str = os.getcwd()
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
patterns: set[str] | None = None,
|
|
28
|
+
ignore_patterns: set[str] | None = None,
|
|
29
|
+
debounce_delay: float = 0.5,
|
|
30
|
+
restart_callback: Callable[[], None] | None = None,
|
|
31
|
+
):
|
|
32
|
+
"""Initialize the reload handler.
|
|
33
|
+
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
patterns : set[str] | None
|
|
37
|
+
File patterns to watch (e.g., {'.py', '.json'})
|
|
38
|
+
ignore_patterns : set[str] | None
|
|
39
|
+
File patterns to ignore
|
|
40
|
+
debounce_delay : float
|
|
41
|
+
Delay before triggering restart to debounce rapid changes
|
|
42
|
+
restart_callback : Callable[[], None] | None
|
|
43
|
+
Custom restart callback (defaults to os.execv)
|
|
44
|
+
"""
|
|
45
|
+
super().__init__()
|
|
46
|
+
if patterns is None:
|
|
47
|
+
patterns = {".py", ".json", ".yaml", ".yml"}
|
|
48
|
+
self.patterns = patterns
|
|
49
|
+
self.ignore_patterns = ignore_patterns or {
|
|
50
|
+
".pyc",
|
|
51
|
+
".pyo",
|
|
52
|
+
".pyd",
|
|
53
|
+
"__pycache__",
|
|
54
|
+
".git",
|
|
55
|
+
".pytest_cache",
|
|
56
|
+
".mypy_cache",
|
|
57
|
+
".ruff_cache",
|
|
58
|
+
}
|
|
59
|
+
self.debounce_delay = debounce_delay
|
|
60
|
+
self.restart_callback = restart_callback or self._default_restart
|
|
61
|
+
self.debounce_timer: threading.Timer | None = None
|
|
62
|
+
self.last_restart_time = 0.0
|
|
63
|
+
self.min_restart_interval = 2.0 # Minimum seconds between restarts
|
|
64
|
+
|
|
65
|
+
def should_watch_file(self, file_path: str) -> bool:
|
|
66
|
+
"""Check if a file should trigger a reload.
|
|
67
|
+
|
|
68
|
+
Parameters
|
|
69
|
+
----------
|
|
70
|
+
file_path : str
|
|
71
|
+
Path to the file
|
|
72
|
+
|
|
73
|
+
Returns
|
|
74
|
+
-------
|
|
75
|
+
bool
|
|
76
|
+
True if file should be watched
|
|
77
|
+
"""
|
|
78
|
+
if not self.patterns:
|
|
79
|
+
return False
|
|
80
|
+
path = Path(file_path)
|
|
81
|
+
|
|
82
|
+
# Check file extension
|
|
83
|
+
if not any(path.name.endswith(pattern) for pattern in self.patterns):
|
|
84
|
+
return False
|
|
85
|
+
|
|
86
|
+
# Check ignore patterns
|
|
87
|
+
if any(path.name.endswith(ignore) for ignore in self.ignore_patterns):
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
# Also check if ignore patterns are in path parts (for directories)
|
|
91
|
+
if any(ignore in path.parts for ignore in self.ignore_patterns):
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
return True
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def get_src_path(event: FileSystemEvent) -> str:
|
|
98
|
+
"""Get the source path from the event.
|
|
99
|
+
|
|
100
|
+
Parameters
|
|
101
|
+
----------
|
|
102
|
+
event : FileSystemEvent
|
|
103
|
+
The file system event
|
|
104
|
+
|
|
105
|
+
Returns
|
|
106
|
+
-------
|
|
107
|
+
str
|
|
108
|
+
The source path as a string
|
|
109
|
+
"""
|
|
110
|
+
return (
|
|
111
|
+
event.src_path
|
|
112
|
+
if isinstance(event.src_path, str)
|
|
113
|
+
else str(event.src_path)
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def on_modified(self, event: FileSystemEvent) -> None:
|
|
117
|
+
"""Handle file modification events.
|
|
118
|
+
|
|
119
|
+
Parameters
|
|
120
|
+
----------
|
|
121
|
+
event : FileSystemEvent
|
|
122
|
+
The file system event
|
|
123
|
+
"""
|
|
124
|
+
if event.is_directory:
|
|
125
|
+
return
|
|
126
|
+
src_path = self.get_src_path(event)
|
|
127
|
+
if not self.should_watch_file(src_path):
|
|
128
|
+
return
|
|
129
|
+
logger.info("File changed: %s", src_path)
|
|
130
|
+
self._schedule_restart()
|
|
131
|
+
|
|
132
|
+
def on_created(self, event: FileSystemEvent) -> None:
|
|
133
|
+
"""Handle file creation events.
|
|
134
|
+
|
|
135
|
+
Parameters
|
|
136
|
+
----------
|
|
137
|
+
event : FileSystemEvent
|
|
138
|
+
The file system event
|
|
139
|
+
"""
|
|
140
|
+
src_path = self.get_src_path(event)
|
|
141
|
+
if not event.is_directory and self.should_watch_file(src_path):
|
|
142
|
+
logger.info("File created: %s", src_path)
|
|
143
|
+
self._schedule_restart()
|
|
144
|
+
|
|
145
|
+
def on_deleted(self, event: FileSystemEvent) -> None:
|
|
146
|
+
"""Handle file deletion events.
|
|
147
|
+
|
|
148
|
+
Parameters
|
|
149
|
+
----------
|
|
150
|
+
event : FileSystemEvent
|
|
151
|
+
The file system event
|
|
152
|
+
"""
|
|
153
|
+
src_path = self.get_src_path(event)
|
|
154
|
+
if not event.is_directory and self.should_watch_file(
|
|
155
|
+
src_path
|
|
156
|
+
): # pragma: no branch
|
|
157
|
+
logger.info("File deleted: %s", src_path)
|
|
158
|
+
self._schedule_restart()
|
|
159
|
+
|
|
160
|
+
def _schedule_restart(self) -> None:
|
|
161
|
+
"""Schedule a restart with debouncing."""
|
|
162
|
+
current_time = time.time()
|
|
163
|
+
|
|
164
|
+
# Prevent too frequent restarts
|
|
165
|
+
if current_time - self.last_restart_time < self.min_restart_interval:
|
|
166
|
+
logger.debug("Restart throttled due to recent restart")
|
|
167
|
+
return
|
|
168
|
+
|
|
169
|
+
# Cancel previous timer
|
|
170
|
+
if self.debounce_timer:
|
|
171
|
+
self.debounce_timer.cancel()
|
|
172
|
+
|
|
173
|
+
# Schedule new restart
|
|
174
|
+
self.debounce_timer = threading.Timer(
|
|
175
|
+
self.debounce_delay, self._trigger_restart
|
|
176
|
+
)
|
|
177
|
+
self.debounce_timer.start()
|
|
178
|
+
logger.debug("Restart scheduled in %ss", self.debounce_delay)
|
|
179
|
+
|
|
180
|
+
def _trigger_restart(self) -> None:
|
|
181
|
+
"""Trigger the actual restart."""
|
|
182
|
+
self.last_restart_time = time.time()
|
|
183
|
+
logger.info("Triggering server restart...")
|
|
184
|
+
try:
|
|
185
|
+
self.restart_callback()
|
|
186
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
|
187
|
+
logger.error("Error during restart: %s", e)
|
|
188
|
+
|
|
189
|
+
@staticmethod
|
|
190
|
+
def _default_restart() -> None:
|
|
191
|
+
"""Restart implementation using os.execv."""
|
|
192
|
+
# Save current working directory
|
|
193
|
+
# pylint: disable=too-many-try-statements,broad-exception-caught
|
|
194
|
+
try:
|
|
195
|
+
# Save current working directory
|
|
196
|
+
os.chdir(ReloadHandler._cwd)
|
|
197
|
+
|
|
198
|
+
# Give time for cleanup
|
|
199
|
+
time.sleep(0.1)
|
|
200
|
+
|
|
201
|
+
# Restart the process with same arguments
|
|
202
|
+
python_path = sys.executable
|
|
203
|
+
args = [python_path] + sys.argv
|
|
204
|
+
|
|
205
|
+
logger.info("Restarting with: %s", " ".join(args))
|
|
206
|
+
|
|
207
|
+
# Use os.execv to replace current process
|
|
208
|
+
# noinspection SpawnShellInjection
|
|
209
|
+
os.execv(python_path, args) # nosemgrep # nosec
|
|
210
|
+
|
|
211
|
+
except Exception as e:
|
|
212
|
+
logger.error("Failed to restart: %s", e)
|
|
213
|
+
# Force exit if restart fails
|
|
214
|
+
os._exit(1) # nosec
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class FileWatcher:
|
|
218
|
+
"""File watcher with auto-reload functionality."""
|
|
219
|
+
|
|
220
|
+
def __init__(
|
|
221
|
+
self,
|
|
222
|
+
watch_dirs: list[Path],
|
|
223
|
+
patterns: set[str] | None = None,
|
|
224
|
+
ignore_patterns: set[str] | None = None,
|
|
225
|
+
debounce_delay: float = 0.5,
|
|
226
|
+
restart_callback: Callable[[], None] | None = None,
|
|
227
|
+
):
|
|
228
|
+
"""Initialize the file watcher.
|
|
229
|
+
|
|
230
|
+
Parameters
|
|
231
|
+
----------
|
|
232
|
+
watch_dirs : list[Path]
|
|
233
|
+
Directories to watch for changes
|
|
234
|
+
patterns : set[str] | None
|
|
235
|
+
File patterns to watch
|
|
236
|
+
ignore_patterns : set[str] | None
|
|
237
|
+
File patterns to ignore
|
|
238
|
+
debounce_delay : float
|
|
239
|
+
Debounce delay for restart
|
|
240
|
+
restart_callback : Callable[[], None] | None
|
|
241
|
+
Custom restart callback
|
|
242
|
+
"""
|
|
243
|
+
self.watch_dirs = watch_dirs
|
|
244
|
+
self.handler = ReloadHandler(
|
|
245
|
+
patterns=patterns,
|
|
246
|
+
ignore_patterns=ignore_patterns,
|
|
247
|
+
debounce_delay=debounce_delay,
|
|
248
|
+
restart_callback=restart_callback,
|
|
249
|
+
)
|
|
250
|
+
self.observer = Observer()
|
|
251
|
+
self._is_watching = False
|
|
252
|
+
|
|
253
|
+
def start(self) -> None:
|
|
254
|
+
"""Start watching for file changes."""
|
|
255
|
+
if self._is_watching:
|
|
256
|
+
logger.warning("File watcher is already running")
|
|
257
|
+
return
|
|
258
|
+
|
|
259
|
+
for watch_dir in self.watch_dirs:
|
|
260
|
+
if watch_dir.exists():
|
|
261
|
+
self.observer.schedule(
|
|
262
|
+
self.handler, str(watch_dir), recursive=True
|
|
263
|
+
)
|
|
264
|
+
logger.info("Watching directory: %s", watch_dir)
|
|
265
|
+
else:
|
|
266
|
+
logger.warning("Watch directory does not exist: %s", watch_dir)
|
|
267
|
+
|
|
268
|
+
self.observer.start()
|
|
269
|
+
self._is_watching = True
|
|
270
|
+
logger.info("File watcher started")
|
|
271
|
+
|
|
272
|
+
def stop(self) -> None:
|
|
273
|
+
"""Stop watching for file changes."""
|
|
274
|
+
if not self._is_watching:
|
|
275
|
+
return
|
|
276
|
+
|
|
277
|
+
self.observer.stop()
|
|
278
|
+
self.observer.join(timeout=5.0)
|
|
279
|
+
self._is_watching = False
|
|
280
|
+
logger.info("File watcher stopped")
|
|
281
|
+
|
|
282
|
+
def __enter__(self) -> "FileWatcher":
|
|
283
|
+
"""Context manager entry.
|
|
284
|
+
|
|
285
|
+
Returns
|
|
286
|
+
-------
|
|
287
|
+
FileWatcher
|
|
288
|
+
The file watcher instance
|
|
289
|
+
"""
|
|
290
|
+
self.start()
|
|
291
|
+
return self
|
|
292
|
+
|
|
293
|
+
def __exit__(
|
|
294
|
+
self,
|
|
295
|
+
exc_type: type[BaseException] | None,
|
|
296
|
+
exc_val: BaseException | None,
|
|
297
|
+
exc_tb: TracebackType | None,
|
|
298
|
+
) -> None:
|
|
299
|
+
"""Context manager exit.
|
|
300
|
+
|
|
301
|
+
Parameters
|
|
302
|
+
----------
|
|
303
|
+
exc_type : type[BaseException] | None
|
|
304
|
+
The type of the exception
|
|
305
|
+
exc_val : BaseException | None
|
|
306
|
+
The exception instance
|
|
307
|
+
exc_tb : TracebackType | None
|
|
308
|
+
The traceback object
|
|
309
|
+
"""
|
|
310
|
+
self.stop()
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def create_file_watcher(
|
|
314
|
+
root_dir: Path,
|
|
315
|
+
additional_dirs: list[Path] | None = None,
|
|
316
|
+
**kwargs: Any,
|
|
317
|
+
) -> FileWatcher:
|
|
318
|
+
"""Create a file watcher for typical Waldiez development setup.
|
|
319
|
+
|
|
320
|
+
Parameters
|
|
321
|
+
----------
|
|
322
|
+
root_dir : Path
|
|
323
|
+
Root directory of the project
|
|
324
|
+
additional_dirs : list[Path] | None
|
|
325
|
+
Additional directories to watch
|
|
326
|
+
**kwargs
|
|
327
|
+
Additional arguments for FileWatcher
|
|
328
|
+
|
|
329
|
+
Returns
|
|
330
|
+
-------
|
|
331
|
+
FileWatcher
|
|
332
|
+
Configured file watcher
|
|
333
|
+
"""
|
|
334
|
+
watch_dirs = [root_dir / "waldiez"]
|
|
335
|
+
|
|
336
|
+
if additional_dirs:
|
|
337
|
+
watch_dirs.extend(additional_dirs)
|
|
338
|
+
|
|
339
|
+
# Filter to existing directories
|
|
340
|
+
watch_dirs = [d for d in watch_dirs if d.exists()]
|
|
341
|
+
|
|
342
|
+
return FileWatcher(watch_dirs, **kwargs)
|