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
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0.
|
|
2
|
+
# Copyright (c) 2024 - 2025 Waldiez and contributors.
|
|
3
|
+
# flake8: noqa: G004
|
|
4
|
+
"""Waldiez subprocess runner that inherits from BaseRunner."""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import re
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Callable, Literal
|
|
10
|
+
|
|
11
|
+
from waldiez.models import Waldiez
|
|
12
|
+
|
|
13
|
+
from ..base_runner import WaldiezBaseRunner
|
|
14
|
+
from ._async_runner import AsyncSubprocessRunner
|
|
15
|
+
from ._sync_runner import SyncSubprocessRunner
|
|
16
|
+
|
|
17
|
+
# TODO: check output directory and return the results from the JSON logs
|
|
18
|
+
# in self._run and self._a_run
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class WaldiezSubprocessRunner(WaldiezBaseRunner):
|
|
22
|
+
"""Waldiez runner that uses subprocess execution via standalone runners."""
|
|
23
|
+
|
|
24
|
+
def __init__(
|
|
25
|
+
self,
|
|
26
|
+
waldiez: Waldiez,
|
|
27
|
+
on_output: Callable[[dict[str, Any]], None] | None = None,
|
|
28
|
+
on_input_request: Callable[[str], None] | None = None,
|
|
29
|
+
on_async_output: Callable[[dict[str, Any]], Any] | None = None,
|
|
30
|
+
on_async_input_request: Callable[[str], Any] | None = None,
|
|
31
|
+
output_path: str | Path | None = None,
|
|
32
|
+
uploads_root: str | Path | None = None,
|
|
33
|
+
structured_io: bool = True,
|
|
34
|
+
dot_env: str | Path | None = None,
|
|
35
|
+
input_timeout: float = 120.0,
|
|
36
|
+
**kwargs: Any,
|
|
37
|
+
) -> None:
|
|
38
|
+
"""Initialize subprocess runner that inherits from BaseRunner.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
waldiez : Waldiez
|
|
43
|
+
The Waldiez workflow to run
|
|
44
|
+
on_output : Callable[[dict[str, Any]], None] | None
|
|
45
|
+
Sync callback for handling output messages
|
|
46
|
+
on_input_request : Callable[[str], None] | None
|
|
47
|
+
Sync callback for handling input requests
|
|
48
|
+
on_async_output : Callable[[dict[str, Any]], Any] | None
|
|
49
|
+
Async callback for handling output messages
|
|
50
|
+
on_async_input_request : Callable[[str], Any] | None
|
|
51
|
+
Async callback for handling input requests
|
|
52
|
+
output_path : str | Path | None
|
|
53
|
+
Output path for the workflow
|
|
54
|
+
uploads_root : str | Path | None
|
|
55
|
+
Root directory for uploads
|
|
56
|
+
structured_io : bool
|
|
57
|
+
Whether to use structured I/O (forced to True for subprocess)
|
|
58
|
+
dot_env : str | Path | None
|
|
59
|
+
Path to .env file
|
|
60
|
+
input_timeout : float
|
|
61
|
+
Timeout for user input in seconds
|
|
62
|
+
**kwargs : Any
|
|
63
|
+
Additional arguments for BaseRunner
|
|
64
|
+
"""
|
|
65
|
+
super().__init__(
|
|
66
|
+
waldiez=waldiez,
|
|
67
|
+
output_path=output_path,
|
|
68
|
+
uploads_root=uploads_root,
|
|
69
|
+
structured_io=True, # Always force structured I/O for subprocess
|
|
70
|
+
dot_env=dot_env,
|
|
71
|
+
**kwargs,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Store callbacks
|
|
75
|
+
self.sync_on_output = on_output or self._default_sync_output
|
|
76
|
+
self.sync_on_input_request = (
|
|
77
|
+
on_input_request or self._default_sync_input_request
|
|
78
|
+
)
|
|
79
|
+
self.async_on_output = on_async_output or self._default_async_output
|
|
80
|
+
self.async_on_input_request = (
|
|
81
|
+
on_async_input_request or self._default_async_input_request
|
|
82
|
+
)
|
|
83
|
+
self.input_timeout = input_timeout
|
|
84
|
+
|
|
85
|
+
# Subprocess runner instances
|
|
86
|
+
self.async_runner: AsyncSubprocessRunner | None = None
|
|
87
|
+
self.sync_runner: SyncSubprocessRunner | None = None
|
|
88
|
+
self.temp_flow_file: Path | None = None
|
|
89
|
+
mode = kwargs.get("mode", "run")
|
|
90
|
+
if mode not in ["run", "debug"]:
|
|
91
|
+
raise ValueError(f"Invalid mode: {mode}")
|
|
92
|
+
self.mode: Literal["run", "debug"] = mode
|
|
93
|
+
waldiez_file = kwargs.get("waldiez_file")
|
|
94
|
+
self._waldiez_file = self._ensure_waldiez_file(waldiez_file)
|
|
95
|
+
|
|
96
|
+
def _ensure_waldiez_file(self, waldiez_file: str | Path | None) -> Path:
|
|
97
|
+
"""Ensure the Waldiez file is a Path object."""
|
|
98
|
+
if isinstance(waldiez_file, str):
|
|
99
|
+
waldiez_file = Path(waldiez_file)
|
|
100
|
+
if waldiez_file and waldiez_file.is_file():
|
|
101
|
+
return waldiez_file.resolve()
|
|
102
|
+
file_name = self.waldiez.name
|
|
103
|
+
# sanitize file name
|
|
104
|
+
file_name = re.sub(r"[^a-zA-Z0-9_\-\.]", "_", file_name)[:30]
|
|
105
|
+
file_name = f"{file_name}.waldiez"
|
|
106
|
+
with open(file_name, "w", encoding="utf-8") as f:
|
|
107
|
+
f.write(self.waldiez.model_dump_json())
|
|
108
|
+
return Path(file_name).resolve()
|
|
109
|
+
|
|
110
|
+
def _default_sync_output(self, data: dict[str, Any]) -> None:
|
|
111
|
+
"""Get the default sync output handler."""
|
|
112
|
+
if data.get("type") == "error":
|
|
113
|
+
self.log.error(data.get("data", ""))
|
|
114
|
+
else:
|
|
115
|
+
content = data.get("data", data)
|
|
116
|
+
self._print(content)
|
|
117
|
+
|
|
118
|
+
def _default_sync_input_request(self, prompt: str) -> None:
|
|
119
|
+
"""Get the default sync input request handler."""
|
|
120
|
+
input_value = input(prompt)
|
|
121
|
+
self.log.debug("User input received: %s", input_value)
|
|
122
|
+
self.provide_user_input(input_value)
|
|
123
|
+
|
|
124
|
+
async def _default_async_output(self, data: dict[str, Any]) -> None:
|
|
125
|
+
"""Get the default async output handler."""
|
|
126
|
+
self._default_sync_output(data)
|
|
127
|
+
|
|
128
|
+
async def _default_async_input_request(self, prompt: str) -> None:
|
|
129
|
+
"""Get the default async input request handler."""
|
|
130
|
+
await asyncio.to_thread(self._default_sync_input_request, prompt)
|
|
131
|
+
|
|
132
|
+
def _create_async_subprocess_runner(self) -> AsyncSubprocessRunner:
|
|
133
|
+
"""Create async subprocess runner."""
|
|
134
|
+
self.async_runner = AsyncSubprocessRunner(
|
|
135
|
+
on_output=self.async_on_output,
|
|
136
|
+
on_input_request=self.async_on_input_request,
|
|
137
|
+
input_timeout=self.input_timeout,
|
|
138
|
+
uploads_root=self.uploads_root,
|
|
139
|
+
dot_env=self.dot_env_path,
|
|
140
|
+
logger=self.log,
|
|
141
|
+
)
|
|
142
|
+
return self.async_runner
|
|
143
|
+
|
|
144
|
+
def _create_sync_subprocess_runner(self) -> SyncSubprocessRunner:
|
|
145
|
+
"""Create sync subprocess runner."""
|
|
146
|
+
self.sync_runner = SyncSubprocessRunner(
|
|
147
|
+
on_output=self.sync_on_output,
|
|
148
|
+
on_input_request=self.sync_on_input_request,
|
|
149
|
+
input_timeout=self.input_timeout,
|
|
150
|
+
uploads_root=self.uploads_root,
|
|
151
|
+
dot_env=self.dot_env_path,
|
|
152
|
+
logger=self.log,
|
|
153
|
+
)
|
|
154
|
+
return self.sync_runner
|
|
155
|
+
|
|
156
|
+
def run(
|
|
157
|
+
self,
|
|
158
|
+
output_path: str | Path | None = None,
|
|
159
|
+
uploads_root: str | Path | None = None,
|
|
160
|
+
structured_io: bool | None = None,
|
|
161
|
+
skip_mmd: bool = False,
|
|
162
|
+
skip_timeline: bool = False,
|
|
163
|
+
dot_env: str | Path | None = None,
|
|
164
|
+
**kwargs: Any,
|
|
165
|
+
) -> list[dict[str, Any]]:
|
|
166
|
+
"""Run the Waldiez flow.
|
|
167
|
+
|
|
168
|
+
Parameters
|
|
169
|
+
----------
|
|
170
|
+
output_path : str | Path | None
|
|
171
|
+
The output path, by default None.
|
|
172
|
+
uploads_root : str | Path | None
|
|
173
|
+
The runtime uploads root, by default None.
|
|
174
|
+
structured_io : bool
|
|
175
|
+
Whether to use structured IO instead of the default 'input/print'.
|
|
176
|
+
skip_mmd : bool
|
|
177
|
+
Whether to skip generating the mermaid diagram.
|
|
178
|
+
skip_timeline : bool
|
|
179
|
+
Whether to skip generating the timeline JSON.
|
|
180
|
+
dot_env : str | Path | None
|
|
181
|
+
The path to the .env file, if any.
|
|
182
|
+
**kwargs : Any
|
|
183
|
+
Additional keyword arguments for the run method.
|
|
184
|
+
|
|
185
|
+
Returns
|
|
186
|
+
-------
|
|
187
|
+
list[dict[str, Any]]
|
|
188
|
+
The results of the run.
|
|
189
|
+
"""
|
|
190
|
+
temp_dir = Path.cwd()
|
|
191
|
+
output_file = self._get_output_file(output_path)
|
|
192
|
+
if dot_env is not None: # pragma: no cover
|
|
193
|
+
resolved = Path(dot_env).resolve()
|
|
194
|
+
if resolved.is_file():
|
|
195
|
+
WaldiezBaseRunner._dot_env_path = resolved
|
|
196
|
+
return self._run(
|
|
197
|
+
temp_dir=temp_dir,
|
|
198
|
+
output_file=output_file,
|
|
199
|
+
uploads_root=Path(uploads_root) if uploads_root else None,
|
|
200
|
+
skip_mmd=skip_mmd,
|
|
201
|
+
skip_timeline=skip_timeline,
|
|
202
|
+
dot_env=dot_env,
|
|
203
|
+
**kwargs,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
def _get_output_file(
|
|
207
|
+
self,
|
|
208
|
+
output_path: str | Path | None,
|
|
209
|
+
) -> Path:
|
|
210
|
+
"""Get the output file path."""
|
|
211
|
+
if output_path:
|
|
212
|
+
_output_path = Path(output_path)
|
|
213
|
+
if _output_path.is_file():
|
|
214
|
+
return _output_path
|
|
215
|
+
if _output_path.is_dir(): # pragma: no branch
|
|
216
|
+
filename = self._waldiez_file.stem
|
|
217
|
+
return _output_path / f"{filename}.py"
|
|
218
|
+
return self._waldiez_file.with_suffix(".py")
|
|
219
|
+
|
|
220
|
+
def _run(
|
|
221
|
+
self,
|
|
222
|
+
temp_dir: Path,
|
|
223
|
+
output_file: Path,
|
|
224
|
+
uploads_root: Path | None,
|
|
225
|
+
skip_mmd: bool,
|
|
226
|
+
skip_timeline: bool,
|
|
227
|
+
**kwargs: Any,
|
|
228
|
+
) -> list[dict[str, Any]]:
|
|
229
|
+
# pylint: disable=too-many-try-statements,broad-exception-caught
|
|
230
|
+
mode = kwargs.get("mode", self.mode)
|
|
231
|
+
if not isinstance(mode, str) or mode not in [
|
|
232
|
+
"run",
|
|
233
|
+
"debug",
|
|
234
|
+
]: # pragma: no cover
|
|
235
|
+
mode = "run"
|
|
236
|
+
self.mode = mode # type: ignore
|
|
237
|
+
try:
|
|
238
|
+
# Create sync subprocess runner
|
|
239
|
+
runner = self._create_sync_subprocess_runner()
|
|
240
|
+
|
|
241
|
+
# Run subprocess
|
|
242
|
+
success = runner.run_subprocess(self._waldiez_file, mode=self.mode)
|
|
243
|
+
return [
|
|
244
|
+
{
|
|
245
|
+
"success": success,
|
|
246
|
+
"runner": "sync_subprocess",
|
|
247
|
+
"mode": self.mode,
|
|
248
|
+
}
|
|
249
|
+
]
|
|
250
|
+
|
|
251
|
+
except Exception as e:
|
|
252
|
+
self.log.error("Error in sync subprocess execution: %s", e)
|
|
253
|
+
return [
|
|
254
|
+
{
|
|
255
|
+
"error": str(e),
|
|
256
|
+
"runner": "sync_subprocess",
|
|
257
|
+
"mode": self.mode,
|
|
258
|
+
}
|
|
259
|
+
]
|
|
260
|
+
|
|
261
|
+
async def a_run(
|
|
262
|
+
self,
|
|
263
|
+
output_path: str | Path | None = None,
|
|
264
|
+
uploads_root: str | Path | None = None,
|
|
265
|
+
structured_io: bool | None = None,
|
|
266
|
+
skip_mmd: bool = False,
|
|
267
|
+
skip_timeline: bool = False,
|
|
268
|
+
dot_env: str | Path | None = None,
|
|
269
|
+
**kwargs: Any,
|
|
270
|
+
) -> list[dict[str, Any]]:
|
|
271
|
+
"""Run the Waldiez flow asynchronously.
|
|
272
|
+
|
|
273
|
+
Parameters
|
|
274
|
+
----------
|
|
275
|
+
output_path : str | Path | None
|
|
276
|
+
The output path, by default None.
|
|
277
|
+
uploads_root : str | Path | None
|
|
278
|
+
The runtime uploads root.
|
|
279
|
+
structured_io : bool
|
|
280
|
+
Whether to use structured IO instead of the default 'input/print'.
|
|
281
|
+
skip_mmd : bool
|
|
282
|
+
Whether to skip generating the mermaid diagram.
|
|
283
|
+
skip_timeline : bool
|
|
284
|
+
Whether to skip generating the timeline JSON.
|
|
285
|
+
dot_env : str | Path | None
|
|
286
|
+
The path to the .env file, if any.
|
|
287
|
+
**kwargs : Any
|
|
288
|
+
Additional keyword arguments for the a_run method.
|
|
289
|
+
|
|
290
|
+
Returns
|
|
291
|
+
-------
|
|
292
|
+
list[dict[str, Any]]
|
|
293
|
+
The results of the run.
|
|
294
|
+
"""
|
|
295
|
+
if dot_env is not None: # pragma: no cover
|
|
296
|
+
resolved = Path(dot_env).resolve()
|
|
297
|
+
if resolved.is_file():
|
|
298
|
+
WaldiezBaseRunner._dot_env_path = resolved
|
|
299
|
+
temp_dir = Path.cwd()
|
|
300
|
+
output_file = self._get_output_file(output_path)
|
|
301
|
+
return await self._a_run(
|
|
302
|
+
temp_dir=temp_dir,
|
|
303
|
+
output_file=output_file,
|
|
304
|
+
uploads_root=Path(uploads_root) if uploads_root else None,
|
|
305
|
+
skip_mmd=skip_mmd,
|
|
306
|
+
skip_timeline=skip_timeline,
|
|
307
|
+
dot_env=dot_env,
|
|
308
|
+
**kwargs,
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
async def _a_run(
|
|
312
|
+
self,
|
|
313
|
+
temp_dir: Path,
|
|
314
|
+
output_file: Path,
|
|
315
|
+
uploads_root: Path | None,
|
|
316
|
+
skip_mmd: bool,
|
|
317
|
+
skip_timeline: bool,
|
|
318
|
+
**kwargs: Any,
|
|
319
|
+
) -> list[dict[str, Any]]:
|
|
320
|
+
"""Run workflow using async subprocess runner.
|
|
321
|
+
|
|
322
|
+
Parameters
|
|
323
|
+
----------
|
|
324
|
+
temp_dir : Path
|
|
325
|
+
Temporary directory
|
|
326
|
+
output_file : Path
|
|
327
|
+
Output file path
|
|
328
|
+
uploads_root : Path | None
|
|
329
|
+
Uploads root directory
|
|
330
|
+
skip_mmd : bool
|
|
331
|
+
Skip mermaid diagram generation
|
|
332
|
+
skip_timeline : bool
|
|
333
|
+
Skip timeline generation
|
|
334
|
+
**kwargs : Any
|
|
335
|
+
Additional arguments
|
|
336
|
+
|
|
337
|
+
Returns
|
|
338
|
+
-------
|
|
339
|
+
list[dict[str, Any]]
|
|
340
|
+
Results of the workflow execution
|
|
341
|
+
"""
|
|
342
|
+
mode = kwargs.get("mode", self.mode)
|
|
343
|
+
if not isinstance(mode, str) or mode not in [
|
|
344
|
+
"run",
|
|
345
|
+
"debug",
|
|
346
|
+
]: # pragma: no cover
|
|
347
|
+
mode = "run"
|
|
348
|
+
self.mode = mode # type: ignore
|
|
349
|
+
|
|
350
|
+
# pylint: disable=too-many-try-statements,broad-exception-caught
|
|
351
|
+
try:
|
|
352
|
+
# Create async subprocess runner
|
|
353
|
+
runner = self._create_async_subprocess_runner()
|
|
354
|
+
|
|
355
|
+
# Run subprocess
|
|
356
|
+
success = await runner.run_subprocess(
|
|
357
|
+
self._waldiez_file,
|
|
358
|
+
mode=self.mode,
|
|
359
|
+
)
|
|
360
|
+
return [
|
|
361
|
+
{
|
|
362
|
+
"success": success,
|
|
363
|
+
"runner": "async_subprocess",
|
|
364
|
+
"mode": self.mode,
|
|
365
|
+
}
|
|
366
|
+
]
|
|
367
|
+
|
|
368
|
+
except Exception as e:
|
|
369
|
+
self.log.error("Error in async subprocess execution: %s", e)
|
|
370
|
+
return [
|
|
371
|
+
{
|
|
372
|
+
"error": str(e),
|
|
373
|
+
"runner": "async_subprocess",
|
|
374
|
+
"mode": self.mode,
|
|
375
|
+
}
|
|
376
|
+
]
|
|
377
|
+
|
|
378
|
+
def provide_user_input(self, user_input: str) -> None:
|
|
379
|
+
"""Provide user input to the active subprocess runner.
|
|
380
|
+
|
|
381
|
+
Parameters
|
|
382
|
+
----------
|
|
383
|
+
user_input : str
|
|
384
|
+
User input response
|
|
385
|
+
"""
|
|
386
|
+
if self.async_runner and self.async_runner.is_running():
|
|
387
|
+
asyncio.create_task(
|
|
388
|
+
self.async_runner.provide_user_input(user_input)
|
|
389
|
+
)
|
|
390
|
+
if self.sync_runner and self.sync_runner.is_running():
|
|
391
|
+
self.sync_runner.provide_user_input(user_input)
|
|
392
|
+
|
|
393
|
+
async def a_provide_user_input(self, user_input: str) -> None:
|
|
394
|
+
"""Provide user input to the active subprocess runner (async version).
|
|
395
|
+
|
|
396
|
+
Parameters
|
|
397
|
+
----------
|
|
398
|
+
user_input : str
|
|
399
|
+
User input response
|
|
400
|
+
"""
|
|
401
|
+
if self.async_runner and self.async_runner.is_running():
|
|
402
|
+
await self.async_runner.provide_user_input(user_input)
|
|
403
|
+
if self.sync_runner and self.sync_runner.is_running():
|
|
404
|
+
await asyncio.to_thread(
|
|
405
|
+
self.sync_runner.provide_user_input, user_input
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
def stop(self) -> None:
|
|
409
|
+
"""Stop the workflow execution."""
|
|
410
|
+
super().stop() # Set the base runner stop flag
|
|
411
|
+
|
|
412
|
+
# Stop active subprocess runners
|
|
413
|
+
if self.async_runner and self.async_runner.is_running():
|
|
414
|
+
asyncio.create_task(self.async_runner.stop())
|
|
415
|
+
if self.sync_runner and self.sync_runner.is_running():
|
|
416
|
+
self.sync_runner.stop()
|
|
417
|
+
|
|
418
|
+
async def a_stop(self) -> None:
|
|
419
|
+
"""Stop the workflow execution (async version)."""
|
|
420
|
+
super().stop() # Set the base runner stop flag
|
|
421
|
+
|
|
422
|
+
# Stop active subprocess runners
|
|
423
|
+
if self.async_runner and self.async_runner.is_running():
|
|
424
|
+
await self.async_runner.stop()
|
|
425
|
+
if self.sync_runner and self.sync_runner.is_running():
|
|
426
|
+
await asyncio.to_thread(self.sync_runner.stop)
|
|
427
|
+
|
|
428
|
+
def is_subprocess_running(self) -> bool:
|
|
429
|
+
"""Check if a subprocess is currently running.
|
|
430
|
+
|
|
431
|
+
Returns
|
|
432
|
+
-------
|
|
433
|
+
bool
|
|
434
|
+
True if a subprocess is running, False otherwise
|
|
435
|
+
"""
|
|
436
|
+
return (
|
|
437
|
+
self.async_runner is not None and self.async_runner.is_running()
|
|
438
|
+
) or (self.sync_runner is not None and self.sync_runner.is_running())
|
|
439
|
+
|
|
440
|
+
def get_subprocess_exit_code(self) -> int | None:
|
|
441
|
+
"""Get the exit code of the subprocess.
|
|
442
|
+
|
|
443
|
+
Returns
|
|
444
|
+
-------
|
|
445
|
+
int | None
|
|
446
|
+
Exit code if available, None otherwise
|
|
447
|
+
"""
|
|
448
|
+
if self.sync_runner:
|
|
449
|
+
return self.sync_runner.get_exit_code()
|
|
450
|
+
return None
|
|
451
|
+
|
|
452
|
+
def _cleanup_subprocess_runners(self) -> None:
|
|
453
|
+
"""Cleanup subprocess runner instances."""
|
|
454
|
+
if self.async_runner and self.async_runner.is_running():
|
|
455
|
+
asyncio.create_task(self.async_runner.stop())
|
|
456
|
+
self.async_runner = None
|
|
457
|
+
if self.sync_runner and self.sync_runner.is_running():
|
|
458
|
+
self.sync_runner.stop()
|
|
459
|
+
self.sync_runner = None
|
|
460
|
+
|
|
461
|
+
def _after_run(
|
|
462
|
+
self,
|
|
463
|
+
results: list[dict[str, Any]],
|
|
464
|
+
output_file: Path,
|
|
465
|
+
uploads_root: Path | None,
|
|
466
|
+
temp_dir: Path,
|
|
467
|
+
skip_mmd: bool,
|
|
468
|
+
skip_timeline: bool,
|
|
469
|
+
) -> None:
|
|
470
|
+
"""Actions to perform after running the flow.
|
|
471
|
+
|
|
472
|
+
Parameters
|
|
473
|
+
----------
|
|
474
|
+
results : list[dict[str, Any]]
|
|
475
|
+
Results from the workflow execution
|
|
476
|
+
output_file : Path
|
|
477
|
+
Output file path
|
|
478
|
+
uploads_root : Path | None
|
|
479
|
+
Uploads root directory
|
|
480
|
+
temp_dir : Path
|
|
481
|
+
Temporary directory
|
|
482
|
+
skip_mmd : bool
|
|
483
|
+
Skip mermaid diagram generation
|
|
484
|
+
skip_timeline : bool
|
|
485
|
+
Skip timeline generation
|
|
486
|
+
"""
|
|
487
|
+
# Cleanup subprocess runners
|
|
488
|
+
self._cleanup_subprocess_runners()
|
|
489
|
+
|
|
490
|
+
async def _a_after_run(
|
|
491
|
+
self,
|
|
492
|
+
results: list[dict[str, Any]],
|
|
493
|
+
output_file: Path,
|
|
494
|
+
uploads_root: Path | None,
|
|
495
|
+
temp_dir: Path,
|
|
496
|
+
skip_mmd: bool,
|
|
497
|
+
skip_timeline: bool,
|
|
498
|
+
) -> None:
|
|
499
|
+
"""Actions to perform after running the flow (async version).
|
|
500
|
+
|
|
501
|
+
Parameters
|
|
502
|
+
----------
|
|
503
|
+
results : list[dict[str, Any]]
|
|
504
|
+
Results from the workflow execution
|
|
505
|
+
output_file : Path
|
|
506
|
+
Output file path
|
|
507
|
+
uploads_root : Path | None
|
|
508
|
+
Uploads root directory
|
|
509
|
+
temp_dir : Path
|
|
510
|
+
Temporary directory
|
|
511
|
+
skip_mmd : bool
|
|
512
|
+
Skip mermaid diagram generation
|
|
513
|
+
skip_timeline : bool
|
|
514
|
+
Skip timeline generation
|
|
515
|
+
"""
|
|
516
|
+
# Cleanup subprocess runners
|
|
517
|
+
self._cleanup_subprocess_runners()
|
|
518
|
+
|
|
519
|
+
@classmethod
|
|
520
|
+
def create_with_callbacks(
|
|
521
|
+
cls,
|
|
522
|
+
waldiez_file: str | Path,
|
|
523
|
+
on_output: Callable[[dict[str, Any]], None] | None = None,
|
|
524
|
+
on_input_request: Callable[[str], None] | None = None,
|
|
525
|
+
on_async_output: Callable[[dict[str, Any]], Any] | None = None,
|
|
526
|
+
on_async_input_request: Callable[[str], Any] | None = None,
|
|
527
|
+
**kwargs: Any,
|
|
528
|
+
) -> "WaldiezSubprocessRunner":
|
|
529
|
+
"""Create subprocess runner from waldiez file with callbacks.
|
|
530
|
+
|
|
531
|
+
Parameters
|
|
532
|
+
----------
|
|
533
|
+
waldiez_file : str | Path
|
|
534
|
+
Path to waldiez file
|
|
535
|
+
on_output : Callable[[dict[str, Any]], None] | None
|
|
536
|
+
Sync output callback
|
|
537
|
+
on_input_request : Callable[[str], None] | None
|
|
538
|
+
Sync input request callback
|
|
539
|
+
on_async_output : Callable[[dict[str, Any]], Any] | None
|
|
540
|
+
Async output callback
|
|
541
|
+
on_async_input_request : Callable[[str], Any] | None
|
|
542
|
+
Async input request callback
|
|
543
|
+
**kwargs : Any
|
|
544
|
+
Additional arguments
|
|
545
|
+
|
|
546
|
+
Returns
|
|
547
|
+
-------
|
|
548
|
+
WaldiezSubprocessRunner
|
|
549
|
+
Configured subprocess runner
|
|
550
|
+
"""
|
|
551
|
+
waldiez = Waldiez.load(waldiez_file)
|
|
552
|
+
kwargs.pop("waldiez_file", None)
|
|
553
|
+
return cls(
|
|
554
|
+
waldiez=waldiez,
|
|
555
|
+
on_output=on_output,
|
|
556
|
+
on_input_request=on_input_request,
|
|
557
|
+
on_async_output=on_async_output,
|
|
558
|
+
on_async_input_request=on_async_input_request,
|
|
559
|
+
waldiez_file=waldiez_file,
|
|
560
|
+
**kwargs,
|
|
561
|
+
)
|