camel-ai 0.2.72a10__py3-none-any.whl → 0.2.73__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 camel-ai might be problematic. Click here for more details.

Files changed (52) hide show
  1. camel/__init__.py +1 -1
  2. camel/agents/chat_agent.py +140 -345
  3. camel/memories/agent_memories.py +18 -17
  4. camel/societies/__init__.py +2 -0
  5. camel/societies/workforce/prompts.py +36 -10
  6. camel/societies/workforce/single_agent_worker.py +7 -5
  7. camel/societies/workforce/workforce.py +6 -4
  8. camel/storages/key_value_storages/mem0_cloud.py +48 -47
  9. camel/storages/vectordb_storages/__init__.py +1 -0
  10. camel/storages/vectordb_storages/surreal.py +100 -150
  11. camel/toolkits/__init__.py +6 -1
  12. camel/toolkits/base.py +60 -2
  13. camel/toolkits/excel_toolkit.py +153 -64
  14. camel/toolkits/file_write_toolkit.py +67 -0
  15. camel/toolkits/hybrid_browser_toolkit/config_loader.py +136 -413
  16. camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit.py +131 -1966
  17. camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit_ts.py +1177 -0
  18. camel/toolkits/hybrid_browser_toolkit/ts/package-lock.json +4356 -0
  19. camel/toolkits/hybrid_browser_toolkit/ts/package.json +33 -0
  20. camel/toolkits/hybrid_browser_toolkit/ts/src/browser-scripts.js +125 -0
  21. camel/toolkits/hybrid_browser_toolkit/ts/src/browser-session.ts +945 -0
  22. camel/toolkits/hybrid_browser_toolkit/ts/src/config-loader.ts +226 -0
  23. camel/toolkits/hybrid_browser_toolkit/ts/src/hybrid-browser-toolkit.ts +522 -0
  24. camel/toolkits/hybrid_browser_toolkit/ts/src/index.ts +7 -0
  25. camel/toolkits/hybrid_browser_toolkit/ts/src/types.ts +110 -0
  26. camel/toolkits/hybrid_browser_toolkit/ts/tsconfig.json +26 -0
  27. camel/toolkits/hybrid_browser_toolkit/ts/websocket-server.js +254 -0
  28. camel/toolkits/hybrid_browser_toolkit/ws_wrapper.py +582 -0
  29. camel/toolkits/hybrid_browser_toolkit_py/__init__.py +17 -0
  30. camel/toolkits/hybrid_browser_toolkit_py/config_loader.py +447 -0
  31. camel/toolkits/hybrid_browser_toolkit_py/hybrid_browser_toolkit.py +2077 -0
  32. camel/toolkits/mcp_toolkit.py +341 -46
  33. camel/toolkits/message_integration.py +719 -0
  34. camel/toolkits/notion_mcp_toolkit.py +234 -0
  35. camel/toolkits/screenshot_toolkit.py +116 -31
  36. camel/toolkits/search_toolkit.py +20 -2
  37. camel/toolkits/slack_toolkit.py +43 -48
  38. camel/toolkits/terminal_toolkit.py +288 -46
  39. camel/toolkits/video_analysis_toolkit.py +13 -13
  40. camel/toolkits/video_download_toolkit.py +11 -11
  41. camel/toolkits/web_deploy_toolkit.py +207 -12
  42. camel/types/enums.py +6 -0
  43. {camel_ai-0.2.72a10.dist-info → camel_ai-0.2.73.dist-info}/METADATA +49 -9
  44. {camel_ai-0.2.72a10.dist-info → camel_ai-0.2.73.dist-info}/RECORD +52 -35
  45. /camel/toolkits/{hybrid_browser_toolkit → hybrid_browser_toolkit_py}/actions.py +0 -0
  46. /camel/toolkits/{hybrid_browser_toolkit → hybrid_browser_toolkit_py}/agent.py +0 -0
  47. /camel/toolkits/{hybrid_browser_toolkit → hybrid_browser_toolkit_py}/browser_session.py +0 -0
  48. /camel/toolkits/{hybrid_browser_toolkit → hybrid_browser_toolkit_py}/snapshot.py +0 -0
  49. /camel/toolkits/{hybrid_browser_toolkit → hybrid_browser_toolkit_py}/stealth_script.js +0 -0
  50. /camel/toolkits/{hybrid_browser_toolkit → hybrid_browser_toolkit_py}/unified_analyzer.js +0 -0
  51. {camel_ai-0.2.72a10.dist-info → camel_ai-0.2.73.dist-info}/WHEEL +0 -0
  52. {camel_ai-0.2.72a10.dist-info → camel_ai-0.2.73.dist-info}/licenses/LICENSE +0 -0
@@ -17,6 +17,7 @@ import os
17
17
  import platform
18
18
  import queue
19
19
  import shutil
20
+ import signal
20
21
  import subprocess
21
22
  import sys
22
23
  import threading
@@ -42,7 +43,7 @@ class TerminalToolkit(BaseToolkit):
42
43
 
43
44
  Args:
44
45
  timeout (Optional[float]): The timeout for terminal operations.
45
- (default: :obj:`None`)
46
+ (default: :obj:`20.0`)
46
47
  shell_sessions (Optional[Dict[str, Any]]): A dictionary to store
47
48
  shell session information. If :obj:`None`, an empty dictionary
48
49
  will be used. (default: :obj:`None`)
@@ -61,6 +62,10 @@ class TerminalToolkit(BaseToolkit):
61
62
  environment. (default: :obj:`False`)
62
63
  safe_mode (bool): Whether to enable safe mode to restrict
63
64
  operations. (default: :obj:`True`)
65
+ interactive (bool): Whether to use interactive mode for shell commands,
66
+ connecting them to the terminal's standard input. This is useful
67
+ for commands that require user input, like `ssh`. Interactive mode
68
+ is only supported on macOS and Linux. (default: :obj:`False`)
64
69
 
65
70
  Note:
66
71
  Most functions are compatible with Unix-based systems (macOS, Linux).
@@ -70,14 +75,17 @@ class TerminalToolkit(BaseToolkit):
70
75
 
71
76
  def __init__(
72
77
  self,
73
- timeout: Optional[float] = None,
78
+ timeout: Optional[float] = 20.0,
74
79
  shell_sessions: Optional[Dict[str, Any]] = None,
75
80
  working_directory: Optional[str] = None,
76
81
  need_terminal: bool = True,
77
82
  use_shell_mode: bool = True,
78
83
  clone_current_env: bool = False,
79
84
  safe_mode: bool = True,
85
+ interactive: bool = False,
80
86
  ):
87
+ # Store timeout before calling super().__init__
88
+ self._timeout = timeout
81
89
  super().__init__(timeout=timeout)
82
90
  self.shell_sessions = shell_sessions or {}
83
91
  self.os_type = platform.system()
@@ -90,11 +98,13 @@ class TerminalToolkit(BaseToolkit):
90
98
  self.cloned_env_path = None
91
99
  self.use_shell_mode = use_shell_mode
92
100
  self._human_takeover_active = False
101
+ self.interactive = interactive
93
102
 
94
103
  self.python_executable = sys.executable
95
104
  self.is_macos = platform.system() == 'Darwin'
96
105
  self.initial_env_path: Optional[str] = None
97
106
  self.initial_env_prepared = False
107
+ self.uv_path: Optional[str] = None
98
108
 
99
109
  atexit.register(self.__del__)
100
110
 
@@ -197,37 +207,111 @@ class TerminalToolkit(BaseToolkit):
197
207
  f"Creating new Python environment at: {self.cloned_env_path}\n"
198
208
  )
199
209
 
200
- # Create virtual environment with pip
201
- venv.create(self.cloned_env_path, with_pip=True)
210
+ # Try to use uv if available
211
+ if self._ensure_uv_available():
212
+ # Use uv to create environment with current Python version
213
+ uv_command = self.uv_path if self.uv_path else "uv"
202
214
 
203
- # Ensure pip is properly available by upgrading it
204
- if self.os_type == 'Windows':
205
- python_path = os.path.join(
206
- self.cloned_env_path, "Scripts", "python.exe"
215
+ # Get current Python version
216
+ current_version = (
217
+ f"{sys.version_info.major}.{sys.version_info.minor}"
207
218
  )
208
- else:
209
- python_path = os.path.join(
210
- self.cloned_env_path, "bin", "python"
219
+
220
+ subprocess.run(
221
+ [
222
+ uv_command,
223
+ "venv",
224
+ "--python",
225
+ current_version,
226
+ self.cloned_env_path,
227
+ ],
228
+ check=True,
229
+ capture_output=True,
230
+ cwd=self.working_dir,
231
+ timeout=300,
211
232
  )
212
233
 
213
- # Verify python executable exists
214
- if os.path.exists(python_path):
215
- # Use python -m pip to ensure pip is available
234
+ # Get the python path from the new environment
235
+ if self.os_type == 'Windows':
236
+ python_path = os.path.join(
237
+ self.cloned_env_path, "Scripts", "python.exe"
238
+ )
239
+ else:
240
+ python_path = os.path.join(
241
+ self.cloned_env_path, "bin", "python"
242
+ )
243
+
244
+ # Install pip and setuptools using uv
216
245
  subprocess.run(
217
- [python_path, "-m", "pip", "install", "--upgrade", "pip"],
246
+ [
247
+ uv_command,
248
+ "pip",
249
+ "install",
250
+ "--python",
251
+ python_path,
252
+ "pip",
253
+ "setuptools",
254
+ "wheel",
255
+ ],
218
256
  check=True,
219
257
  capture_output=True,
220
258
  cwd=self.working_dir,
221
- timeout=60,
259
+ timeout=300,
222
260
  )
261
+
223
262
  self._update_terminal_output(
224
- "New Python environment created successfully with pip!\n"
263
+ "[UV] Cloned Python environment created successfully!\n"
225
264
  )
265
+
226
266
  else:
267
+ # Fallback to standard venv
227
268
  self._update_terminal_output(
228
- f"Warning: Python executable not found at {python_path}\n"
269
+ "Falling back to standard venv for cloning environment\n"
270
+ )
271
+
272
+ # Create virtual environment with pip. On macOS, use
273
+ # symlinks=False to avoid dyld library loading issues
274
+ venv.create(
275
+ self.cloned_env_path, with_pip=True, symlinks=False
229
276
  )
230
277
 
278
+ # Ensure pip is properly available by upgrading it
279
+ if self.os_type == 'Windows':
280
+ python_path = os.path.join(
281
+ self.cloned_env_path, "Scripts", "python.exe"
282
+ )
283
+ else:
284
+ python_path = os.path.join(
285
+ self.cloned_env_path, "bin", "python"
286
+ )
287
+
288
+ # Verify python executable exists
289
+ if os.path.exists(python_path):
290
+ # Use python -m pip to ensure pip is available
291
+ subprocess.run(
292
+ [
293
+ python_path,
294
+ "-m",
295
+ "pip",
296
+ "install",
297
+ "--upgrade",
298
+ "pip",
299
+ ],
300
+ check=True,
301
+ capture_output=True,
302
+ cwd=self.working_dir,
303
+ timeout=60,
304
+ )
305
+ self._update_terminal_output(
306
+ "New Python environment created successfully "
307
+ "with pip!\n"
308
+ )
309
+ else:
310
+ self._update_terminal_output(
311
+ f"Warning: Python executable not found "
312
+ f"at {python_path}\n"
313
+ )
314
+
231
315
  except subprocess.CalledProcessError as e:
232
316
  error_msg = e.stderr.decode() if e.stderr else str(e)
233
317
  self._update_terminal_output(
@@ -252,6 +336,97 @@ class TerminalToolkit(BaseToolkit):
252
336
  or shutil.which("uv") is not None
253
337
  )
254
338
 
339
+ def _ensure_uv_available(self) -> bool:
340
+ r"""Ensure uv is available, installing it if necessary.
341
+
342
+ Returns:
343
+ bool: True if uv is available (either already installed or
344
+ successfully installed), False otherwise.
345
+ """
346
+ # Check if uv is already available
347
+ existing_uv = shutil.which("uv")
348
+ if existing_uv is not None:
349
+ self.uv_path = existing_uv
350
+ self._update_terminal_output(
351
+ f"uv is already available at: {self.uv_path}\n"
352
+ )
353
+ return True
354
+
355
+ try:
356
+ self._update_terminal_output("uv not found, installing...\n")
357
+
358
+ # Install uv using the official installer script
359
+ if self.os_type in ['Darwin', 'Linux']:
360
+ # Use curl to download and execute the installer
361
+ install_cmd = "curl -LsSf https://astral.sh/uv/install.sh | sh"
362
+ result = subprocess.run(
363
+ install_cmd,
364
+ shell=True,
365
+ capture_output=True,
366
+ text=True,
367
+ timeout=60,
368
+ )
369
+
370
+ if result.returncode != 0:
371
+ self._update_terminal_output(
372
+ f"Failed to install uv: {result.stderr}\n"
373
+ )
374
+ return False
375
+
376
+ # Check if uv was installed in the expected location
377
+ home = os.path.expanduser("~")
378
+ uv_bin_path = os.path.join(home, ".cargo", "bin")
379
+ uv_executable = os.path.join(uv_bin_path, "uv")
380
+
381
+ if os.path.exists(uv_executable):
382
+ # Store the full path to uv instead of modifying PATH
383
+ self.uv_path = uv_executable
384
+ self._update_terminal_output(
385
+ f"uv installed successfully at: {self.uv_path}\n"
386
+ )
387
+ return True
388
+
389
+ elif self.os_type == 'Windows':
390
+ # Use PowerShell to install uv on Windows
391
+ install_cmd = (
392
+ "powershell -ExecutionPolicy Bypass -c "
393
+ "\"irm https://astral.sh/uv/install.ps1 | iex\""
394
+ )
395
+ result = subprocess.run(
396
+ install_cmd,
397
+ shell=True,
398
+ capture_output=True,
399
+ text=True,
400
+ timeout=60,
401
+ )
402
+
403
+ if result.returncode != 0:
404
+ self._update_terminal_output(
405
+ f"Failed to install uv: {result.stderr}\n"
406
+ )
407
+ return False
408
+
409
+ # Check if uv was installed in the expected location on Windows
410
+ home = os.path.expanduser("~")
411
+ uv_bin_path = os.path.join(home, ".cargo", "bin")
412
+ uv_executable = os.path.join(uv_bin_path, "uv.exe")
413
+
414
+ if os.path.exists(uv_executable):
415
+ # Store the full path to uv instead of modifying PATH
416
+ self.uv_path = uv_executable
417
+ self._update_terminal_output(
418
+ f"uv installed successfully at: {self.uv_path}\n"
419
+ )
420
+ return True
421
+
422
+ self._update_terminal_output("Failed to verify uv installation\n")
423
+ return False
424
+
425
+ except Exception as e:
426
+ self._update_terminal_output(f"Error installing uv: {e!s}\n")
427
+ logger.error(f"Failed to install uv: {e}")
428
+ return False
429
+
255
430
  def _prepare_initial_environment(self):
256
431
  r"""Prepare initial environment with Python 3.10, pip, and other
257
432
  essential tools.
@@ -276,10 +451,14 @@ class TerminalToolkit(BaseToolkit):
276
451
  # Create the initial environment directory
277
452
  os.makedirs(self.initial_env_path, exist_ok=True)
278
453
 
279
- # Check if we should use uv
280
- if self._is_uv_environment():
454
+ # Try to ensure uv is available and use it preferentially
455
+ if self._ensure_uv_available():
281
456
  self._setup_initial_env_with_uv()
282
457
  else:
458
+ # Fallback to venv if uv installation failed
459
+ self._update_terminal_output(
460
+ "Falling back to standard venv for environment setup\n"
461
+ )
283
462
  self._setup_initial_env_with_venv()
284
463
 
285
464
  self.initial_env_prepared = True
@@ -299,9 +478,18 @@ class TerminalToolkit(BaseToolkit):
299
478
  raise Exception("Initial environment path not set")
300
479
 
301
480
  try:
481
+ # Use the stored uv path if available, otherwise fall back to "uv"
482
+ uv_command = self.uv_path if self.uv_path else "uv"
483
+
302
484
  # Create virtual environment with Python 3.10 using uv
303
485
  subprocess.run(
304
- ["uv", "venv", "--python", "3.10", self.initial_env_path],
486
+ [
487
+ uv_command,
488
+ "venv",
489
+ "--python",
490
+ "3.10",
491
+ self.initial_env_path,
492
+ ],
305
493
  check=True,
306
494
  capture_output=True,
307
495
  cwd=self.working_dir,
@@ -319,10 +507,17 @@ class TerminalToolkit(BaseToolkit):
319
507
  )
320
508
 
321
509
  # Install essential packages using uv
322
- essential_packages = ["pip", "setuptools", "wheel"]
510
+ essential_packages = [
511
+ "pip",
512
+ "setuptools",
513
+ "wheel",
514
+ "pyautogui",
515
+ "plotly",
516
+ "ffmpeg",
517
+ ]
323
518
  subprocess.run(
324
519
  [
325
- "uv",
520
+ uv_command,
326
521
  "pip",
327
522
  "install",
328
523
  "--python",
@@ -356,10 +551,12 @@ class TerminalToolkit(BaseToolkit):
356
551
 
357
552
  try:
358
553
  # Create virtual environment with system Python
554
+ # On macOS, use symlinks=False to avoid dyld library loading issues
359
555
  venv.create(
360
556
  self.initial_env_path,
361
557
  with_pip=True,
362
558
  system_site_packages=False,
559
+ symlinks=False,
363
560
  )
364
561
 
365
562
  # Get pip path
@@ -371,7 +568,14 @@ class TerminalToolkit(BaseToolkit):
371
568
  pip_path = os.path.join(self.initial_env_path, "bin", "pip")
372
569
 
373
570
  # Upgrade pip and install essential packages
374
- essential_packages = ["pip", "setuptools", "wheel"]
571
+ essential_packages = [
572
+ "pip",
573
+ "setuptools",
574
+ "wheel",
575
+ "pyautogui",
576
+ "plotly",
577
+ "ffmpeg",
578
+ ]
375
579
  subprocess.run(
376
580
  [pip_path, "install", "--upgrade", *essential_packages],
377
581
  check=True,
@@ -804,26 +1008,18 @@ class TerminalToolkit(BaseToolkit):
804
1008
 
805
1009
  return True, command
806
1010
 
807
- def shell_exec(
808
- self, id: str, command: str, interactive: bool = False
809
- ) -> str:
1011
+ def shell_exec(self, id: str, command: str) -> str:
810
1012
  r"""Executes a shell command in a specified session.
811
1013
 
812
1014
  This function creates and manages shell sessions to execute commands,
813
- simulating a real terminal. It can run commands in both non-interactive
814
- (capturing output) and interactive modes. Each session is identified by
815
- a unique ID. If a session with the given ID does not exist, it will be
816
- created.
1015
+ simulating a real terminal. The behavior depends on the toolkit's
1016
+ interactive mode setting. Each session is identified by a unique ID.
1017
+ If a session with the given ID does not exist, it will be created.
817
1018
 
818
1019
  Args:
819
1020
  id (str): A unique identifier for the shell session. This is used
820
1021
  to manage multiple concurrent shell processes.
821
1022
  command (str): The shell command to be executed.
822
- interactive (bool, optional): If `True`, the command runs in
823
- interactive mode, connecting it to the terminal's standard
824
- input. This is useful for commands that require user input,
825
- like `ssh`. Defaults to `False`. Interactive mode is only
826
- supported on macOS and Linux. (default: :obj:`False`)
827
1023
 
828
1024
  Returns:
829
1025
  str: The standard output and standard error from the command. If an
@@ -831,8 +1027,8 @@ class TerminalToolkit(BaseToolkit):
831
1027
  returned.
832
1028
 
833
1029
  Note:
834
- When `interactive` is set to `True`, this function may block if the
835
- command requires input. In safe mode, some commands that are
1030
+ When the toolkit is initialized with interactive mode, commands may
1031
+ block if they require input. In safe mode, some commands that are
836
1032
  considered dangerous are restricted.
837
1033
  """
838
1034
  error_msg = self._enforce_working_dir_for_execution(self.working_dir)
@@ -929,7 +1125,12 @@ class TerminalToolkit(BaseToolkit):
929
1125
  elif pip_path and command.startswith('pip'):
930
1126
  command = command.replace('pip', pip_path, 1)
931
1127
 
932
- if not interactive:
1128
+ if not self.interactive:
1129
+ # Use preexec_fn to create a new process group on Unix systems
1130
+ preexec_fn = None
1131
+ if self.os_type in ['Darwin', 'Linux']:
1132
+ preexec_fn = os.setsid
1133
+
933
1134
  proc = subprocess.Popen(
934
1135
  command,
935
1136
  shell=True,
@@ -941,17 +1142,55 @@ class TerminalToolkit(BaseToolkit):
941
1142
  bufsize=1,
942
1143
  universal_newlines=True,
943
1144
  env=os.environ.copy(),
1145
+ preexec_fn=preexec_fn,
944
1146
  )
945
1147
 
946
1148
  self.shell_sessions[id]["process"] = proc
947
1149
  self.shell_sessions[id]["running"] = True
948
- stdout, stderr = proc.communicate()
949
- output = stdout or ""
950
- if stderr:
951
- output += f"\nStderr Output:\n{stderr}"
952
- self.shell_sessions[id]["output"] = output
953
- self._update_terminal_output(output + "\n")
954
- return output
1150
+ try:
1151
+ # Use the instance timeout if available
1152
+ stdout, stderr = proc.communicate(timeout=self.timeout)
1153
+ output = stdout or ""
1154
+ if stderr:
1155
+ output += f"\nStderr Output:\n{stderr}"
1156
+ self.shell_sessions[id]["output"] = output
1157
+ self.shell_sessions[id]["running"] = False
1158
+ self._update_terminal_output(output + "\n")
1159
+ return output
1160
+ except subprocess.TimeoutExpired:
1161
+ # Kill the entire process group on Unix systems
1162
+ if self.os_type in ['Darwin', 'Linux']:
1163
+ try:
1164
+ os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
1165
+ # Give it a short time to terminate
1166
+ stdout, stderr = proc.communicate(timeout=1)
1167
+ except subprocess.TimeoutExpired:
1168
+ # Force kill the process group
1169
+ os.killpg(os.getpgid(proc.pid), signal.SIGKILL)
1170
+ stdout, stderr = proc.communicate()
1171
+ except ProcessLookupError:
1172
+ # Process already dead
1173
+ stdout, stderr = proc.communicate()
1174
+ else:
1175
+ # Windows fallback
1176
+ proc.terminate()
1177
+ try:
1178
+ stdout, stderr = proc.communicate(timeout=1)
1179
+ except subprocess.TimeoutExpired:
1180
+ proc.kill()
1181
+ stdout, stderr = proc.communicate()
1182
+
1183
+ output = stdout or ""
1184
+ if stderr:
1185
+ output += f"\nStderr Output:\n{stderr}"
1186
+ error_msg = (
1187
+ f"\nCommand timed out after {self.timeout} seconds"
1188
+ )
1189
+ output += error_msg
1190
+ self.shell_sessions[id]["output"] = output
1191
+ self.shell_sessions[id]["running"] = False
1192
+ self._update_terminal_output(output + "\n")
1193
+ return output
955
1194
 
956
1195
  # Interactive mode with real-time streaming via PTY
957
1196
  if self.os_type not in ['Darwin', 'Linux']:
@@ -1061,6 +1300,9 @@ class TerminalToolkit(BaseToolkit):
1061
1300
  f"Detailed information: {detailed_error}"
1062
1301
  )
1063
1302
 
1303
+ # Mark shell_exec to skip automatic timeout wrapping
1304
+ shell_exec._manual_timeout = True # type: ignore[attr-defined]
1305
+
1064
1306
  def shell_view(self, id: str) -> str:
1065
1307
  r"""View the full output history of a specified shell session.
1066
1308
 
@@ -97,7 +97,7 @@ class VideoAnalysisToolkit(BaseToolkit):
97
97
  r"""A class for analysing videos with vision-language model.
98
98
 
99
99
  Args:
100
- download_directory (Optional[str], optional): The directory where the
100
+ working_directory (Optional[str], optional): The directory where the
101
101
  video will be downloaded to. If not provided, video will be stored
102
102
  in a temporary directory and will be cleaned up after use.
103
103
  (default: :obj:`None`)
@@ -123,7 +123,7 @@ class VideoAnalysisToolkit(BaseToolkit):
123
123
  @dependencies_required("ffmpeg", "scenedetect")
124
124
  def __init__(
125
125
  self,
126
- download_directory: Optional[str] = None,
126
+ working_directory: Optional[str] = None,
127
127
  model: Optional[BaseModelBackend] = None,
128
128
  use_audio_transcription: bool = False,
129
129
  use_ocr: bool = False,
@@ -133,30 +133,30 @@ class VideoAnalysisToolkit(BaseToolkit):
133
133
  timeout: Optional[float] = None,
134
134
  ) -> None:
135
135
  super().__init__(timeout=timeout)
136
- self._cleanup = download_directory is None
136
+ self._cleanup = working_directory is None
137
137
  self._temp_files: list[str] = [] # Track temporary files for cleanup
138
138
  self._use_audio_transcription = use_audio_transcription
139
139
  self._use_ocr = use_ocr
140
140
  self.output_language = output_language
141
141
  self.frame_interval = frame_interval
142
142
 
143
- self._download_directory = Path(
144
- download_directory or tempfile.mkdtemp()
143
+ self._working_directory = Path(
144
+ working_directory or tempfile.mkdtemp()
145
145
  ).resolve()
146
146
 
147
147
  self.video_downloader_toolkit = VideoDownloaderToolkit(
148
- download_directory=str(self._download_directory),
148
+ working_directory=str(self._working_directory),
149
149
  cookies_path=cookies_path,
150
150
  )
151
151
 
152
152
  try:
153
- self._download_directory.mkdir(parents=True, exist_ok=True)
153
+ self._working_directory.mkdir(parents=True, exist_ok=True)
154
154
  except OSError as e:
155
155
  raise ValueError(
156
- f"Error creating directory {self._download_directory}: {e}"
156
+ f"Error creating directory {self._working_directory}: {e}"
157
157
  )
158
158
 
159
- logger.info(f"Video will be downloaded to {self._download_directory}")
159
+ logger.info(f"Video will be downloaded to {self._working_directory}")
160
160
 
161
161
  self.vl_model = model
162
162
  # Ensure ChatAgent is initialized with a model if provided
@@ -206,16 +206,16 @@ class VideoAnalysisToolkit(BaseToolkit):
206
206
  )
207
207
 
208
208
  # Clean up temporary directory if needed
209
- if self._cleanup and os.path.exists(self._download_directory):
209
+ if self._cleanup and os.path.exists(self._working_directory):
210
210
  try:
211
211
  import sys
212
212
 
213
213
  if getattr(sys, 'modules', None) is not None:
214
214
  import shutil
215
215
 
216
- shutil.rmtree(self._download_directory)
216
+ shutil.rmtree(self._working_directory)
217
217
  logger.debug(
218
- f"Removed temp directory: {self._download_directory}"
218
+ f"Removed temp directory: {self._working_directory}"
219
219
  )
220
220
  except (ImportError, AttributeError):
221
221
  # Skip cleanup if interpreter is shutting down
@@ -223,7 +223,7 @@ class VideoAnalysisToolkit(BaseToolkit):
223
223
  except OSError as e:
224
224
  logger.warning(
225
225
  f"Failed to remove temporary directory "
226
- f"{self._download_directory}: {e}"
226
+ f"{self._working_directory}: {e}"
227
227
  )
228
228
 
229
229
  @dependencies_required("pytesseract", "cv2", "numpy")
@@ -62,7 +62,7 @@ class VideoDownloaderToolkit(BaseToolkit):
62
62
  chunks.
63
63
 
64
64
  Args:
65
- download_directory (Optional[str], optional): The directory where the
65
+ working_directory (Optional[str], optional): The directory where the
66
66
  video will be downloaded to. If not provided, video will be stored
67
67
  in a temporary directory and will be cleaned up after use.
68
68
  (default: :obj:`None`)
@@ -73,30 +73,30 @@ class VideoDownloaderToolkit(BaseToolkit):
73
73
  @dependencies_required("yt_dlp", "ffmpeg")
74
74
  def __init__(
75
75
  self,
76
- download_directory: Optional[str] = None,
76
+ working_directory: Optional[str] = None,
77
77
  cookies_path: Optional[str] = None,
78
78
  timeout: Optional[float] = None,
79
79
  ) -> None:
80
80
  super().__init__(timeout=timeout)
81
- self._cleanup = download_directory is None
81
+ self._cleanup = working_directory is None
82
82
  self._cookies_path = cookies_path
83
83
 
84
- self._download_directory = Path(
85
- download_directory or tempfile.mkdtemp()
84
+ self._working_directory = Path(
85
+ working_directory or tempfile.mkdtemp()
86
86
  ).resolve()
87
87
 
88
88
  try:
89
- self._download_directory.mkdir(parents=True, exist_ok=True)
89
+ self._working_directory.mkdir(parents=True, exist_ok=True)
90
90
  except FileExistsError:
91
91
  raise ValueError(
92
- f"{self._download_directory} is not a valid directory."
92
+ f"{self._working_directory} is not a valid directory."
93
93
  )
94
94
  except OSError as e:
95
95
  raise ValueError(
96
- f"Error creating directory {self._download_directory}: {e}"
96
+ f"Error creating directory {self._working_directory}: {e}"
97
97
  )
98
98
 
99
- logger.info(f"Video will be downloaded to {self._download_directory}")
99
+ logger.info(f"Video will be downloaded to {self._working_directory}")
100
100
 
101
101
  def __del__(self) -> None:
102
102
  r"""Deconstructor for the VideoDownloaderToolkit class.
@@ -111,7 +111,7 @@ class VideoDownloaderToolkit(BaseToolkit):
111
111
  if getattr(sys, 'modules', None) is not None:
112
112
  import shutil
113
113
 
114
- shutil.rmtree(self._download_directory, ignore_errors=True)
114
+ shutil.rmtree(self._working_directory, ignore_errors=True)
115
115
  except (ImportError, AttributeError):
116
116
  # Skip cleanup if interpreter is shutting down
117
117
  pass
@@ -130,7 +130,7 @@ class VideoDownloaderToolkit(BaseToolkit):
130
130
  """
131
131
  import yt_dlp
132
132
 
133
- video_template = self._download_directory / "%(title)s.%(ext)s"
133
+ video_template = self._working_directory / "%(title)s.%(ext)s"
134
134
  ydl_opts = {
135
135
  'format': 'bestvideo+bestaudio/best',
136
136
  'outtmpl': str(video_template),