waldiez 0.5.9__py3-none-any.whl → 0.6.0__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 +113 -24
- waldiez/exporting/agent/exporter.py +9 -6
- waldiez/exporting/agent/extras/captain_agent_extras.py +44 -7
- waldiez/exporting/agent/extras/group_manager_agent_extas.py +6 -1
- waldiez/exporting/agent/extras/handoffs/after_work.py +1 -0
- waldiez/exporting/agent/extras/handoffs/available.py +1 -0
- waldiez/exporting/agent/extras/handoffs/condition.py +3 -1
- waldiez/exporting/agent/extras/handoffs/handoff.py +1 -0
- waldiez/exporting/agent/extras/handoffs/target.py +1 -0
- waldiez/exporting/agent/termination.py +1 -0
- waldiez/exporting/chats/utils/common.py +25 -23
- waldiez/exporting/core/__init__.py +0 -2
- waldiez/exporting/core/constants.py +3 -1
- waldiez/exporting/core/context.py +13 -13
- waldiez/exporting/core/extras/serializer.py +12 -10
- waldiez/exporting/core/protocols.py +0 -141
- waldiez/exporting/core/result.py +5 -5
- waldiez/exporting/core/types.py +1 -0
- waldiez/exporting/core/utils/llm_config.py +2 -2
- waldiez/exporting/flow/execution_generator.py +1 -0
- waldiez/exporting/flow/merger.py +2 -2
- waldiez/exporting/flow/orchestrator.py +1 -0
- waldiez/exporting/flow/utils/common.py +3 -3
- waldiez/exporting/flow/utils/importing.py +1 -0
- waldiez/exporting/flow/utils/logging.py +7 -80
- waldiez/exporting/tools/exporter.py +5 -0
- waldiez/exporting/tools/factory.py +4 -0
- waldiez/exporting/tools/processor.py +5 -1
- waldiez/io/__init__.py +3 -1
- waldiez/io/_ws.py +15 -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 +98 -45
- waldiez/io/utils.py +17 -11
- waldiez/io/ws.py +10 -12
- waldiez/logger.py +180 -63
- waldiez/models/agents/agent/agent.py +2 -1
- waldiez/models/agents/agent/update_system_message.py +0 -2
- waldiez/models/agents/doc_agent/doc_agent.py +8 -1
- waldiez/models/chat/chat.py +1 -0
- waldiez/models/chat/chat_data.py +0 -2
- waldiez/models/common/base.py +2 -0
- waldiez/models/common/dict_utils.py +169 -40
- waldiez/models/common/handoff.py +2 -0
- waldiez/models/common/method_utils.py +2 -0
- waldiez/models/flow/flow.py +6 -6
- waldiez/models/flow/info.py +5 -1
- waldiez/models/model/_llm.py +31 -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 +477 -0
- waldiez/models/tool/predefined/_google.py +4 -1
- waldiez/models/tool/predefined/_perplexity.py +4 -1
- waldiez/models/tool/predefined/_searxng.py +4 -1
- waldiez/models/tool/predefined/_tavily.py +4 -1
- waldiez/models/tool/predefined/_wikipedia.py +5 -2
- 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 +381 -363
- waldiez/running/environment.py +1 -0
- waldiez/running/exceptions.py +9 -0
- waldiez/running/post_run.py +10 -4
- waldiez/running/pre_run.py +199 -66
- waldiez/running/protocol.py +21 -101
- waldiez/running/run_results.py +1 -1
- waldiez/running/standard_runner.py +83 -276
- waldiez/running/step_by_step/__init__.py +46 -0
- waldiez/running/step_by_step/breakpoints_mixin.py +512 -0
- waldiez/running/step_by_step/command_handler.py +151 -0
- waldiez/running/step_by_step/events_processor.py +199 -0
- waldiez/running/step_by_step/step_by_step_models.py +541 -0
- waldiez/running/step_by_step/step_by_step_runner.py +750 -0
- waldiez/running/subprocess_runner/__base__.py +279 -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 +456 -0
- waldiez/running/subprocess_runner/runner.py +570 -0
- waldiez/running/timeline_processor.py +1 -1
- waldiez/running/utils.py +492 -3
- waldiez/utils/version.py +2 -6
- waldiez/ws/__init__.py +71 -0
- waldiez/ws/__main__.py +15 -0
- waldiez/ws/_file_handler.py +199 -0
- waldiez/ws/_mock.py +74 -0
- waldiez/ws/cli.py +235 -0
- waldiez/ws/client_manager.py +851 -0
- waldiez/ws/errors.py +416 -0
- waldiez/ws/models.py +988 -0
- waldiez/ws/reloader.py +363 -0
- waldiez/ws/server.py +508 -0
- waldiez/ws/session_manager.py +393 -0
- waldiez/ws/session_stats.py +83 -0
- waldiez/ws/utils.py +410 -0
- {waldiez-0.5.9.dist-info → waldiez-0.6.0.dist-info}/METADATA +105 -96
- {waldiez-0.5.9.dist-info → waldiez-0.6.0.dist-info}/RECORD +108 -83
- waldiez/running/patch_io_stream.py +0 -210
- {waldiez-0.5.9.dist-info → waldiez-0.6.0.dist-info}/WHEEL +0 -0
- {waldiez-0.5.9.dist-info → waldiez-0.6.0.dist-info}/entry_points.txt +0 -0
- {waldiez-0.5.9.dist-info → waldiez-0.6.0.dist-info}/licenses/LICENSE +0 -0
- {waldiez-0.5.9.dist-info → waldiez-0.6.0.dist-info}/licenses/NOTICE.md +0 -0
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
|
@@ -10,7 +10,6 @@ import threading
|
|
|
10
10
|
from getpass import getpass
|
|
11
11
|
from pathlib import Path
|
|
12
12
|
from typing import Any
|
|
13
|
-
from uuid import uuid4
|
|
14
13
|
|
|
15
14
|
from autogen.events import BaseEvent # type: ignore
|
|
16
15
|
from autogen.io import IOStream # type: ignore
|
|
@@ -23,6 +22,9 @@ from .models import (
|
|
|
23
22
|
UserResponse,
|
|
24
23
|
)
|
|
25
24
|
from .utils import (
|
|
25
|
+
DEBUG_INPUT_PROMPT,
|
|
26
|
+
START_CHAT_PROMPT,
|
|
27
|
+
MessageType,
|
|
26
28
|
gen_id,
|
|
27
29
|
get_image,
|
|
28
30
|
get_message_dump,
|
|
@@ -63,29 +65,43 @@ class StructuredIOStream(IOStream):
|
|
|
63
65
|
"""
|
|
64
66
|
sep = kwargs.get("sep", " ")
|
|
65
67
|
end = kwargs.get("end", "\n")
|
|
68
|
+
flush = kwargs.get("flush", True)
|
|
69
|
+
payload_type = kwargs.get("type", "print")
|
|
66
70
|
message = sep.join(map(str, args))
|
|
67
|
-
|
|
68
|
-
|
|
71
|
+
if len(args) == 1 and isinstance(args[0], dict):
|
|
72
|
+
message = args[0] # pyright: ignore
|
|
73
|
+
payload_type = message.get("type", payload_type) # pyright: ignore
|
|
74
|
+
is_dumped = True
|
|
75
|
+
else:
|
|
76
|
+
is_dumped, message = is_json_dumped(message)
|
|
77
|
+
if is_dumped:
|
|
69
78
|
# If the message is already JSON-dumped,
|
|
70
79
|
# let's try not to double dump it
|
|
71
|
-
message = json.loads(message)
|
|
72
80
|
payload: dict[str, Any] = {
|
|
73
|
-
"
|
|
74
|
-
"id": uuid4().hex,
|
|
81
|
+
"id": gen_id(),
|
|
75
82
|
"timestamp": now(),
|
|
76
|
-
"data": message,
|
|
83
|
+
# "data": message,
|
|
77
84
|
}
|
|
85
|
+
if isinstance(message, dict):
|
|
86
|
+
payload.update(message) # pyright: ignore
|
|
87
|
+
else:
|
|
88
|
+
payload["data"] = message
|
|
89
|
+
if "type" not in payload:
|
|
90
|
+
payload["type"] = payload_type
|
|
78
91
|
else:
|
|
79
|
-
message
|
|
80
|
-
payload =
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
92
|
+
print_message = PrintMessage(data=message) # pyright: ignore
|
|
93
|
+
payload = print_message.model_dump(mode="json", fallback=str)
|
|
94
|
+
payload["type"] = payload_type
|
|
95
|
+
dumped = json.dumps(payload, default=str, ensure_ascii=False) + end
|
|
96
|
+
if kwargs.get("file") and kwargs["file"] in [
|
|
97
|
+
sys.stderr,
|
|
98
|
+
sys.__stderr__,
|
|
99
|
+
sys.stdout,
|
|
100
|
+
sys.__stdout__,
|
|
101
|
+
]:
|
|
102
|
+
print(dumped, file=kwargs["file"], flush=flush)
|
|
103
|
+
else:
|
|
104
|
+
print(dumped, flush=flush)
|
|
89
105
|
|
|
90
106
|
def input(self, prompt: str = "", *, password: bool = False) -> str:
|
|
91
107
|
"""Structured input from stdin.
|
|
@@ -102,36 +118,27 @@ class StructuredIOStream(IOStream):
|
|
|
102
118
|
str
|
|
103
119
|
The line read from the input stream.
|
|
104
120
|
"""
|
|
105
|
-
request_id =
|
|
121
|
+
request_id = gen_id()
|
|
106
122
|
prompt = prompt or ">"
|
|
107
|
-
if not prompt or prompt in [">", "> "]:
|
|
123
|
+
if not prompt or prompt in [">", "> "]: # pragma: no cover
|
|
108
124
|
# if the prompt is just ">" or "> ",
|
|
109
125
|
# let's use a more descriptive one
|
|
110
|
-
prompt =
|
|
111
|
-
|
|
112
|
-
|
|
126
|
+
prompt = START_CHAT_PROMPT
|
|
127
|
+
input_type = "chat"
|
|
128
|
+
if prompt.strip() == DEBUG_INPUT_PROMPT.strip():
|
|
129
|
+
input_type = "debug"
|
|
130
|
+
self._send_input_request(
|
|
131
|
+
prompt,
|
|
132
|
+
request_id,
|
|
133
|
+
password,
|
|
134
|
+
input_type=input_type,
|
|
135
|
+
)
|
|
113
136
|
user_input_raw = self._read_user_input(prompt, password, request_id)
|
|
114
137
|
response = self._handle_user_input(user_input_raw, request_id)
|
|
115
138
|
user_response = response.to_string(
|
|
116
139
|
uploads_root=self.uploads_root,
|
|
117
140
|
base_name=request_id,
|
|
118
141
|
)
|
|
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
142
|
return user_response
|
|
136
143
|
|
|
137
144
|
# noinspection PyMethodMayBeStatic
|
|
@@ -168,14 +175,21 @@ class StructuredIOStream(IOStream):
|
|
|
168
175
|
self,
|
|
169
176
|
prompt: str,
|
|
170
177
|
request_id: str,
|
|
171
|
-
password: bool,
|
|
178
|
+
password: bool = False,
|
|
179
|
+
input_type: str = "chat",
|
|
172
180
|
) -> None:
|
|
181
|
+
if input_type not in ("chat", "debug"):
|
|
182
|
+
input_type = "chat"
|
|
183
|
+
request_type = (
|
|
184
|
+
"debug_input_request" if input_type == "debug" else "input_request"
|
|
185
|
+
)
|
|
173
186
|
payload = UserInputRequest(
|
|
187
|
+
type=request_type, # type: ignore
|
|
174
188
|
request_id=request_id,
|
|
175
189
|
prompt=prompt,
|
|
176
190
|
password=password,
|
|
177
191
|
).model_dump(mode="json")
|
|
178
|
-
print(json.dumps(payload), flush=True)
|
|
192
|
+
print(json.dumps(payload, default=str), flush=True)
|
|
179
193
|
|
|
180
194
|
def _read_user_input(
|
|
181
195
|
self,
|
|
@@ -205,6 +219,11 @@ class StructuredIOStream(IOStream):
|
|
|
205
219
|
except queue.Empty:
|
|
206
220
|
self._send_timeout_message(request_id)
|
|
207
221
|
return ""
|
|
222
|
+
except BaseException as e:
|
|
223
|
+
self._send_error_message(request_id, str(e))
|
|
224
|
+
return ""
|
|
225
|
+
finally:
|
|
226
|
+
input_thread.join(timeout=1)
|
|
208
227
|
|
|
209
228
|
def _send_timeout_message(self, request_id: str) -> None:
|
|
210
229
|
timeout_payload = {
|
|
@@ -216,6 +235,17 @@ class StructuredIOStream(IOStream):
|
|
|
216
235
|
}
|
|
217
236
|
print(json.dumps(timeout_payload), flush=True, file=sys.stderr)
|
|
218
237
|
|
|
238
|
+
# noinspection PyMethodMayBeStatic
|
|
239
|
+
def _send_error_message(self, request_id: str, error_message: str) -> None:
|
|
240
|
+
error_payload = {
|
|
241
|
+
"id": gen_id(),
|
|
242
|
+
"type": "error",
|
|
243
|
+
"request_id": request_id,
|
|
244
|
+
"timestamp": now(),
|
|
245
|
+
"data": error_message,
|
|
246
|
+
}
|
|
247
|
+
print(json.dumps(error_payload), flush=True, file=sys.stderr)
|
|
248
|
+
|
|
219
249
|
def _handle_user_input(
|
|
220
250
|
self, user_input_raw: str, request_id: str
|
|
221
251
|
) -> UserResponse:
|
|
@@ -294,7 +324,12 @@ class StructuredIOStream(IOStream):
|
|
|
294
324
|
UserResponse
|
|
295
325
|
The structured user response.
|
|
296
326
|
"""
|
|
297
|
-
|
|
327
|
+
response_type: MessageType
|
|
328
|
+
_response_type = user_input.get("type", "input_response")
|
|
329
|
+
if _response_type not in ("input_response", "debug_input_response"):
|
|
330
|
+
response_type = "input_response"
|
|
331
|
+
else:
|
|
332
|
+
response_type = _response_type
|
|
298
333
|
if user_input.get("request_id") == request_id:
|
|
299
334
|
# We have a valid response to our request
|
|
300
335
|
data = user_input.get("data")
|
|
@@ -302,6 +337,7 @@ class StructuredIOStream(IOStream):
|
|
|
302
337
|
# let's check if text|image keys are sent (outside data)
|
|
303
338
|
if "image" in user_input or "text" in user_input:
|
|
304
339
|
return UserResponse(
|
|
340
|
+
type=response_type,
|
|
305
341
|
request_id=request_id,
|
|
306
342
|
data=self._format_multimedia_response(
|
|
307
343
|
request_id=request_id, data=user_input
|
|
@@ -311,10 +347,12 @@ class StructuredIOStream(IOStream):
|
|
|
311
347
|
return self._handle_list_response(
|
|
312
348
|
data, # pyright: ignore
|
|
313
349
|
request_id=request_id,
|
|
350
|
+
response_type=response_type,
|
|
314
351
|
)
|
|
315
352
|
if not data or not isinstance(data, (str, dict)):
|
|
316
353
|
# No / invalid data provided in the response
|
|
317
354
|
return UserResponse(
|
|
355
|
+
type=response_type,
|
|
318
356
|
request_id=request_id,
|
|
319
357
|
data="",
|
|
320
358
|
)
|
|
@@ -324,6 +362,7 @@ class StructuredIOStream(IOStream):
|
|
|
324
362
|
data = self._load_user_input(data)
|
|
325
363
|
if isinstance(data, dict):
|
|
326
364
|
return UserResponse(
|
|
365
|
+
type=response_type,
|
|
327
366
|
data=self._format_multimedia_response(
|
|
328
367
|
request_id=request_id,
|
|
329
368
|
data=data, # pyright: ignore
|
|
@@ -333,11 +372,14 @@ class StructuredIOStream(IOStream):
|
|
|
333
372
|
# For other types (numbers, bools ,...),
|
|
334
373
|
# let's just convert to string
|
|
335
374
|
return UserResponse(
|
|
336
|
-
|
|
375
|
+
type=response_type,
|
|
376
|
+
data=str(data),
|
|
377
|
+
request_id=request_id,
|
|
337
378
|
) # pragma: no cover
|
|
338
379
|
# This response doesn't match our request_id, log and return empty
|
|
339
380
|
self._log_mismatched_response(request_id, user_input)
|
|
340
381
|
return UserResponse(
|
|
382
|
+
type=response_type,
|
|
341
383
|
request_id=request_id,
|
|
342
384
|
data="",
|
|
343
385
|
)
|
|
@@ -347,10 +389,12 @@ class StructuredIOStream(IOStream):
|
|
|
347
389
|
self,
|
|
348
390
|
data: list[dict[str, Any]],
|
|
349
391
|
request_id: str,
|
|
392
|
+
response_type: MessageType,
|
|
350
393
|
) -> UserResponse:
|
|
351
394
|
if len(data) == 0: # pyright: ignore
|
|
352
395
|
# Empty list, return empty response
|
|
353
396
|
return UserResponse(
|
|
397
|
+
type=response_type,
|
|
354
398
|
request_id=request_id,
|
|
355
399
|
data="",
|
|
356
400
|
)
|
|
@@ -367,10 +411,12 @@ class StructuredIOStream(IOStream):
|
|
|
367
411
|
if not input_data: # pragma: no cover
|
|
368
412
|
# No valid data in the list, return empty response
|
|
369
413
|
return UserResponse(
|
|
414
|
+
type=response_type,
|
|
370
415
|
request_id=request_id,
|
|
371
416
|
data="",
|
|
372
417
|
)
|
|
373
418
|
return UserResponse(
|
|
419
|
+
type=response_type,
|
|
374
420
|
request_id=request_id,
|
|
375
421
|
data=input_data,
|
|
376
422
|
)
|
|
@@ -387,9 +433,16 @@ class StructuredIOStream(IOStream):
|
|
|
387
433
|
The response received
|
|
388
434
|
"""
|
|
389
435
|
# Create a log message
|
|
436
|
+
got_id: str | None = None
|
|
437
|
+
if isinstance(response, dict):
|
|
438
|
+
got_id = response.get("request_id") # pyright: ignore
|
|
439
|
+
response_str = str(response) # pyright: ignore
|
|
440
|
+
message = response_str[:100] + (
|
|
441
|
+
"..." if len(response_str) > 100 else ""
|
|
442
|
+
)
|
|
390
443
|
log_payload: dict[str, Any] = {
|
|
391
444
|
"type": "warning",
|
|
392
|
-
"id":
|
|
445
|
+
"id": gen_id(),
|
|
393
446
|
"timestamp": now(),
|
|
394
447
|
"data": {
|
|
395
448
|
"message": (
|
|
@@ -398,8 +451,8 @@ class StructuredIOStream(IOStream):
|
|
|
398
451
|
),
|
|
399
452
|
"details": {
|
|
400
453
|
"expected_id": expected_id,
|
|
401
|
-
"
|
|
402
|
-
|
|
454
|
+
"received_id": got_id,
|
|
455
|
+
"message": message,
|
|
403
456
|
},
|
|
404
457
|
},
|
|
405
458
|
}
|
waldiez/io/utils.py
CHANGED
|
@@ -14,9 +14,16 @@ from autogen.agentchat.contrib.img_utils import get_pil_image # type: ignore
|
|
|
14
14
|
from autogen.events import BaseEvent # type: ignore
|
|
15
15
|
from autogen.messages import BaseMessage # type: ignore
|
|
16
16
|
|
|
17
|
+
DEBUG_INPUT_PROMPT = (
|
|
18
|
+
"[Step] (c)ontinue, (r)un, (q)uit, (i)nfo, (h)elp, (st)ats: "
|
|
19
|
+
)
|
|
20
|
+
START_CHAT_PROMPT = "Enter your message to start the conversation: "
|
|
21
|
+
|
|
17
22
|
MessageType = Literal[
|
|
18
23
|
"input_request",
|
|
19
24
|
"input_response",
|
|
25
|
+
"debug_input_request",
|
|
26
|
+
"debug_input_response",
|
|
20
27
|
"print",
|
|
21
28
|
"input",
|
|
22
29
|
]
|
|
@@ -131,26 +138,25 @@ def get_image(
|
|
|
131
138
|
return image_data
|
|
132
139
|
|
|
133
140
|
|
|
134
|
-
def is_json_dumped(value:
|
|
135
|
-
"""Check if a
|
|
141
|
+
def is_json_dumped(value: Any) -> tuple[bool, Any]:
|
|
142
|
+
"""Check if a value is JSON-dumped.
|
|
136
143
|
|
|
137
144
|
Parameters
|
|
138
145
|
----------
|
|
139
|
-
value :
|
|
140
|
-
The
|
|
146
|
+
value : Any
|
|
147
|
+
The value to check.
|
|
141
148
|
|
|
142
149
|
Returns
|
|
143
150
|
-------
|
|
144
151
|
bool
|
|
145
|
-
True if the
|
|
152
|
+
True if the value is JSON-dumped, False otherwise.
|
|
146
153
|
"""
|
|
154
|
+
to_check = value.strip() if isinstance(value, str) else value
|
|
147
155
|
try:
|
|
148
|
-
parsed = json.loads(
|
|
149
|
-
|
|
150
|
-
# we consider it JSON-dumped
|
|
151
|
-
return not isinstance(parsed, str)
|
|
156
|
+
parsed = json.loads(to_check)
|
|
157
|
+
return True, parsed
|
|
152
158
|
except json.JSONDecodeError:
|
|
153
|
-
return False
|
|
159
|
+
return False, value
|
|
154
160
|
|
|
155
161
|
|
|
156
162
|
def try_parse_maybe_serialized(value: str) -> Any:
|
|
@@ -170,7 +176,7 @@ def try_parse_maybe_serialized(value: str) -> Any:
|
|
|
170
176
|
"""
|
|
171
177
|
for parser in (json.loads, ast.literal_eval):
|
|
172
178
|
# pylint: disable=broad-exception-caught, too-many-try-statements
|
|
173
|
-
# noinspection PyBroadException
|
|
179
|
+
# noinspection PyBroadException,TryExceptPass
|
|
174
180
|
try:
|
|
175
181
|
parsed: dict[str, Any] | list[Any] | str = parser(value)
|
|
176
182
|
# 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:
|
|
@@ -118,8 +118,8 @@ class AsyncWebsocketsIOStream(IOStream):
|
|
|
118
118
|
end = kwargs.get("end", "\n")
|
|
119
119
|
msg = sep.join(str(arg) for arg in args)
|
|
120
120
|
|
|
121
|
-
is_dumped = is_json_dumped(msg)
|
|
122
|
-
if is_dumped
|
|
121
|
+
is_dumped, msg = is_json_dumped(msg)
|
|
122
|
+
if is_dumped: # 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:
|