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.

Files changed (88) hide show
  1. waldiez/_version.py +1 -1
  2. waldiez/cli.py +112 -24
  3. waldiez/exporting/agent/exporter.py +3 -0
  4. waldiez/exporting/agent/extras/captain_agent_extras.py +44 -7
  5. waldiez/exporting/agent/extras/handoffs/condition.py +3 -1
  6. waldiez/exporting/chats/utils/common.py +25 -23
  7. waldiez/exporting/core/__init__.py +0 -2
  8. waldiez/exporting/core/context.py +13 -13
  9. waldiez/exporting/core/protocols.py +0 -141
  10. waldiez/exporting/core/result.py +5 -5
  11. waldiez/exporting/flow/merger.py +2 -2
  12. waldiez/exporting/flow/orchestrator.py +1 -0
  13. waldiez/exporting/flow/utils/common.py +2 -2
  14. waldiez/exporting/flow/utils/importing.py +1 -0
  15. waldiez/exporting/flow/utils/logging.py +6 -7
  16. waldiez/exporting/tools/exporter.py +5 -0
  17. waldiez/exporting/tools/factory.py +4 -0
  18. waldiez/exporting/tools/processor.py +5 -1
  19. waldiez/io/_ws.py +13 -5
  20. waldiez/io/models/content/image.py +1 -0
  21. waldiez/io/models/user_input.py +4 -4
  22. waldiez/io/models/user_response.py +1 -0
  23. waldiez/io/mqtt.py +1 -1
  24. waldiez/io/structured.py +17 -17
  25. waldiez/io/utils.py +1 -1
  26. waldiez/io/ws.py +9 -11
  27. waldiez/logger.py +180 -63
  28. waldiez/models/agents/agent/update_system_message.py +0 -2
  29. waldiez/models/agents/doc_agent/doc_agent.py +8 -1
  30. waldiez/models/common/dict_utils.py +169 -40
  31. waldiez/models/flow/flow.py +6 -6
  32. waldiez/models/flow/info.py +5 -1
  33. waldiez/models/model/_llm.py +28 -14
  34. waldiez/models/model/model.py +4 -1
  35. waldiez/models/model/model_data.py +18 -5
  36. waldiez/models/tool/predefined/_config.py +5 -1
  37. waldiez/models/tool/predefined/_duckduckgo.py +4 -0
  38. waldiez/models/tool/predefined/_email.py +474 -0
  39. waldiez/models/tool/predefined/_google.py +8 -6
  40. waldiez/models/tool/predefined/_perplexity.py +3 -0
  41. waldiez/models/tool/predefined/_searxng.py +3 -0
  42. waldiez/models/tool/predefined/_tavily.py +4 -1
  43. waldiez/models/tool/predefined/_wikipedia.py +4 -1
  44. waldiez/models/tool/predefined/_youtube.py +4 -1
  45. waldiez/models/tool/predefined/protocol.py +3 -0
  46. waldiez/models/tool/tool.py +22 -4
  47. waldiez/models/waldiez.py +12 -0
  48. waldiez/runner.py +37 -54
  49. waldiez/running/__init__.py +6 -0
  50. waldiez/running/base_runner.py +310 -353
  51. waldiez/running/environment.py +1 -0
  52. waldiez/running/exceptions.py +9 -0
  53. waldiez/running/post_run.py +4 -4
  54. waldiez/running/pre_run.py +51 -40
  55. waldiez/running/protocol.py +21 -101
  56. waldiez/running/run_results.py +1 -1
  57. waldiez/running/standard_runner.py +84 -277
  58. waldiez/running/step_by_step/__init__.py +46 -0
  59. waldiez/running/step_by_step/breakpoints_mixin.py +188 -0
  60. waldiez/running/step_by_step/step_by_step_models.py +224 -0
  61. waldiez/running/step_by_step/step_by_step_runner.py +745 -0
  62. waldiez/running/subprocess_runner/__base__.py +282 -0
  63. waldiez/running/subprocess_runner/__init__.py +16 -0
  64. waldiez/running/subprocess_runner/_async_runner.py +362 -0
  65. waldiez/running/subprocess_runner/_sync_runner.py +455 -0
  66. waldiez/running/subprocess_runner/runner.py +561 -0
  67. waldiez/running/timeline_processor.py +1 -1
  68. waldiez/running/utils.py +376 -1
  69. waldiez/utils/version.py +2 -6
  70. waldiez/ws/__init__.py +70 -0
  71. waldiez/ws/__main__.py +15 -0
  72. waldiez/ws/_file_handler.py +201 -0
  73. waldiez/ws/cli.py +211 -0
  74. waldiez/ws/client_manager.py +835 -0
  75. waldiez/ws/errors.py +416 -0
  76. waldiez/ws/models.py +971 -0
  77. waldiez/ws/reloader.py +342 -0
  78. waldiez/ws/server.py +469 -0
  79. waldiez/ws/session_manager.py +393 -0
  80. waldiez/ws/session_stats.py +83 -0
  81. waldiez/ws/utils.py +385 -0
  82. {waldiez-0.5.8.dist-info → waldiez-0.5.10.dist-info}/METADATA +74 -74
  83. {waldiez-0.5.8.dist-info → waldiez-0.5.10.dist-info}/RECORD +87 -65
  84. waldiez/running/patch_io_stream.py +0 -210
  85. {waldiez-0.5.8.dist-info → waldiez-0.5.10.dist-info}/WHEEL +0 -0
  86. {waldiez-0.5.8.dist-info → waldiez-0.5.10.dist-info}/entry_points.txt +0 -0
  87. {waldiez-0.5.8.dist-info → waldiez-0.5.10.dist-info}/licenses/LICENSE +0 -0
  88. {waldiez-0.5.8.dist-info → waldiez-0.5.10.dist-info}/licenses/NOTICE.md +0 -0
@@ -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,
@@ -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
- f"Import conflict: {conflict}"
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
- f"Environment variable conflict: {conflict}"
312
+ "Environment variable conflict: %s", conflict
313
313
  )
314
314
 
315
315
  return list(env_vars.values())
@@ -249,6 +249,7 @@ class ExportOrchestrator:
249
249
  agent_names=self.agent_names,
250
250
  tools=self.tools,
251
251
  tool_names=self.tool_names,
252
+ is_async=self.waldiez.is_async,
252
253
  output_dir=self.config.output_directory,
253
254
  context=self.context,
254
255
  )
@@ -120,8 +120,8 @@ def main_doc_string() -> str:
120
120
 
121
121
  Raises
122
122
  ------
123
- RuntimeError
124
- If the chat session fails.
123
+ SystemExit
124
+ If the user interrupts the chat session.
125
125
  """'''
126
126
 
127
127
 
@@ -208,6 +208,7 @@ def get_the_imports_string(
208
208
  final_string += "\n"
209
209
 
210
210
  if is_async:
211
+ builtin_imports.insert(0, "import asyncio")
211
212
  final_string += (
212
213
  "\nimport aiofiles"
213
214
  "\nimport aiosqlite"
@@ -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
- content += " data = [dict(zip(column_names, row)) for row in rows]\n"
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} # pylint: disable=import-outside-toplevel\n"
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
- # Handle other exceptions that may occur during receive
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
- # Handle other exceptions that may occur during receive
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
 
@@ -61,6 +61,7 @@ class ImageUrlMediaContent(BaseModel):
61
61
  type: Literal["image_url"] = "image_url"
62
62
  image_url: ImageContent
63
63
 
64
+ # noinspection DuplicatedCode
64
65
  def to_string(
65
66
  self,
66
67
  uploads_root: Path | None,
@@ -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
- # Handle empty response from timeout
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: