camel-ai 0.2.75a6__py3-none-any.whl → 0.2.76__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 (97) hide show
  1. camel/__init__.py +1 -1
  2. camel/agents/chat_agent.py +1001 -205
  3. camel/agents/mcp_agent.py +30 -27
  4. camel/configs/__init__.py +6 -0
  5. camel/configs/amd_config.py +70 -0
  6. camel/configs/cometapi_config.py +104 -0
  7. camel/data_collectors/alpaca_collector.py +15 -6
  8. camel/environments/tic_tac_toe.py +1 -1
  9. camel/interpreters/__init__.py +2 -0
  10. camel/interpreters/docker/Dockerfile +3 -12
  11. camel/interpreters/microsandbox_interpreter.py +395 -0
  12. camel/loaders/__init__.py +11 -2
  13. camel/loaders/chunkr_reader.py +9 -0
  14. camel/memories/__init__.py +2 -1
  15. camel/memories/agent_memories.py +3 -1
  16. camel/memories/blocks/chat_history_block.py +21 -3
  17. camel/memories/records.py +88 -8
  18. camel/messages/base.py +127 -34
  19. camel/models/__init__.py +4 -0
  20. camel/models/amd_model.py +101 -0
  21. camel/models/azure_openai_model.py +0 -6
  22. camel/models/base_model.py +30 -0
  23. camel/models/cometapi_model.py +83 -0
  24. camel/models/model_factory.py +4 -0
  25. camel/models/openai_compatible_model.py +0 -6
  26. camel/models/openai_model.py +0 -6
  27. camel/models/zhipuai_model.py +61 -2
  28. camel/parsers/__init__.py +18 -0
  29. camel/parsers/mcp_tool_call_parser.py +176 -0
  30. camel/retrievers/auto_retriever.py +1 -0
  31. camel/runtimes/daytona_runtime.py +11 -12
  32. camel/societies/workforce/prompts.py +131 -50
  33. camel/societies/workforce/single_agent_worker.py +434 -49
  34. camel/societies/workforce/structured_output_handler.py +30 -18
  35. camel/societies/workforce/task_channel.py +43 -0
  36. camel/societies/workforce/utils.py +105 -12
  37. camel/societies/workforce/workforce.py +1322 -311
  38. camel/societies/workforce/workforce_logger.py +24 -5
  39. camel/storages/key_value_storages/json.py +15 -2
  40. camel/storages/object_storages/google_cloud.py +1 -1
  41. camel/storages/vectordb_storages/oceanbase.py +10 -11
  42. camel/storages/vectordb_storages/tidb.py +8 -6
  43. camel/tasks/task.py +4 -3
  44. camel/toolkits/__init__.py +18 -5
  45. camel/toolkits/aci_toolkit.py +45 -0
  46. camel/toolkits/code_execution.py +28 -1
  47. camel/toolkits/context_summarizer_toolkit.py +684 -0
  48. camel/toolkits/dingtalk.py +1135 -0
  49. camel/toolkits/edgeone_pages_mcp_toolkit.py +11 -31
  50. camel/toolkits/{file_write_toolkit.py → file_toolkit.py} +194 -34
  51. camel/toolkits/function_tool.py +6 -1
  52. camel/toolkits/google_drive_mcp_toolkit.py +12 -31
  53. camel/toolkits/hybrid_browser_toolkit/config_loader.py +12 -0
  54. camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit.py +79 -2
  55. camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit_ts.py +95 -59
  56. camel/toolkits/hybrid_browser_toolkit/installer.py +203 -0
  57. camel/toolkits/hybrid_browser_toolkit/ts/package-lock.json +5 -612
  58. camel/toolkits/hybrid_browser_toolkit/ts/package.json +0 -1
  59. camel/toolkits/hybrid_browser_toolkit/ts/src/browser-session.ts +619 -95
  60. camel/toolkits/hybrid_browser_toolkit/ts/src/config-loader.ts +7 -2
  61. camel/toolkits/hybrid_browser_toolkit/ts/src/hybrid-browser-toolkit.ts +115 -219
  62. camel/toolkits/hybrid_browser_toolkit/ts/src/parent-child-filter.ts +226 -0
  63. camel/toolkits/hybrid_browser_toolkit/ts/src/snapshot-parser.ts +219 -0
  64. camel/toolkits/hybrid_browser_toolkit/ts/src/som-screenshot-injected.ts +543 -0
  65. camel/toolkits/hybrid_browser_toolkit/ts/src/types.ts +1 -0
  66. camel/toolkits/hybrid_browser_toolkit/ts/websocket-server.js +39 -6
  67. camel/toolkits/hybrid_browser_toolkit/ws_wrapper.py +405 -131
  68. camel/toolkits/hybrid_browser_toolkit_py/hybrid_browser_toolkit.py +9 -5
  69. camel/toolkits/{openai_image_toolkit.py → image_generation_toolkit.py} +98 -31
  70. camel/toolkits/markitdown_toolkit.py +27 -1
  71. camel/toolkits/mcp_toolkit.py +348 -348
  72. camel/toolkits/message_integration.py +3 -0
  73. camel/toolkits/minimax_mcp_toolkit.py +195 -0
  74. camel/toolkits/note_taking_toolkit.py +18 -8
  75. camel/toolkits/notion_mcp_toolkit.py +16 -26
  76. camel/toolkits/origene_mcp_toolkit.py +8 -49
  77. camel/toolkits/playwright_mcp_toolkit.py +12 -31
  78. camel/toolkits/resend_toolkit.py +168 -0
  79. camel/toolkits/slack_toolkit.py +50 -1
  80. camel/toolkits/terminal_toolkit/__init__.py +18 -0
  81. camel/toolkits/terminal_toolkit/terminal_toolkit.py +924 -0
  82. camel/toolkits/terminal_toolkit/utils.py +532 -0
  83. camel/toolkits/vertex_ai_veo_toolkit.py +590 -0
  84. camel/toolkits/video_analysis_toolkit.py +17 -11
  85. camel/toolkits/wechat_official_toolkit.py +483 -0
  86. camel/types/enums.py +124 -1
  87. camel/types/unified_model_type.py +5 -0
  88. camel/utils/commons.py +17 -0
  89. camel/utils/context_utils.py +804 -0
  90. camel/utils/mcp.py +136 -2
  91. camel/utils/token_counting.py +25 -17
  92. {camel_ai-0.2.75a6.dist-info → camel_ai-0.2.76.dist-info}/METADATA +158 -59
  93. {camel_ai-0.2.75a6.dist-info → camel_ai-0.2.76.dist-info}/RECORD +95 -76
  94. camel/loaders/pandas_reader.py +0 -368
  95. camel/toolkits/terminal_toolkit.py +0 -1788
  96. {camel_ai-0.2.75a6.dist-info → camel_ai-0.2.76.dist-info}/WHEEL +0 -0
  97. {camel_ai-0.2.75a6.dist-info → camel_ai-0.2.76.dist-info}/licenses/LICENSE +0 -0
@@ -12,14 +12,12 @@
12
12
  # limitations under the License.
13
13
  # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
14
 
15
- from typing import List, Optional
16
-
17
- from camel.toolkits import BaseToolkit, FunctionTool
15
+ from typing import Optional
18
16
 
19
17
  from .mcp_toolkit import MCPToolkit
20
18
 
21
19
 
22
- class EdgeOnePagesMCPToolkit(BaseToolkit):
20
+ class EdgeOnePagesMCPToolkit(MCPToolkit):
23
21
  r"""EdgeOnePagesMCPToolkit provides an interface for interacting with
24
22
  EdgeOne pages using the EdgeOne Pages MCP server.
25
23
 
@@ -38,32 +36,14 @@ class EdgeOnePagesMCPToolkit(BaseToolkit):
38
36
  timeout (Optional[float]): Connection timeout in seconds.
39
37
  (default: :obj:`None`)
40
38
  """
41
- super().__init__(timeout=timeout)
42
-
43
- self._mcp_toolkit = MCPToolkit(
44
- config_dict={
45
- "mcpServers": {
46
- "edgeone-pages-mcp-server": {
47
- "command": "npx",
48
- "args": ["edgeone-pages-mcp"],
49
- }
39
+ config_dict = {
40
+ "mcpServers": {
41
+ "edgeone-pages-mcp-server": {
42
+ "command": "npx",
43
+ "args": ["edgeone-pages-mcp"],
50
44
  }
51
- },
52
- timeout=timeout,
53
- )
54
-
55
- async def connect(self):
56
- r"""Explicitly connect to the EdgeOne Pages MCP server."""
57
- await self._mcp_toolkit.connect()
45
+ }
46
+ }
58
47
 
59
- async def disconnect(self):
60
- r"""Explicitly disconnect from the EdgeOne Pages MCP server."""
61
- await self._mcp_toolkit.disconnect()
62
-
63
- def get_tools(self) -> List[FunctionTool]:
64
- r"""Returns a list of tools provided by the EdgeOnePagesMCPToolkit.
65
-
66
- Returns:
67
- List[FunctionTool]: List of available tools.
68
- """
69
- return self._mcp_toolkit.get_tools()
48
+ # Initialize parent MCPToolkit with EdgeOne Pages configuration
49
+ super().__init__(config_dict=config_dict, timeout=timeout)
@@ -15,7 +15,7 @@ import os
15
15
  import re
16
16
  from datetime import datetime
17
17
  from pathlib import Path
18
- from typing import List, Optional, Tuple, Union
18
+ from typing import Dict, List, Optional, Tuple, Union
19
19
 
20
20
  from camel.logger import get_logger
21
21
  from camel.toolkits.base import BaseToolkit
@@ -26,14 +26,17 @@ logger = get_logger(__name__)
26
26
 
27
27
 
28
28
  @MCPServer()
29
- class FileWriteToolkit(BaseToolkit):
30
- r"""A toolkit for creating, writing, and modifying text in files.
31
-
32
- This class provides cross-platform (macOS, Linux, Windows) support for
33
- writing to various file formats (Markdown, DOCX, PDF, and plaintext),
34
- replacing text in existing files, automatic filename uniquification to
35
- prevent overwrites, custom encoding and enhanced formatting options for
36
- specialized formats.
29
+ class FileToolkit(BaseToolkit):
30
+ r"""A comprehensive toolkit for file operations including reading,
31
+ writing, and editing files.
32
+
33
+ This class provides cross-platform (macOS, Linux, Windows) support for:
34
+ - Reading various file formats (text, JSON, YAML, PDF, DOCX)
35
+ - Writing to multiple formats (Markdown, DOCX, PDF, plaintext, JSON,
36
+ YAML, CSV, HTML)
37
+ - Editing and modifying existing files with content replacement
38
+ - Automatic backup creation before modifications
39
+ - Custom encoding and enhanced formatting options
37
40
  """
38
41
 
39
42
  def __init__(
@@ -126,36 +129,32 @@ class FileWriteToolkit(BaseToolkit):
126
129
  with file_path.open("w", encoding=encoding) as f:
127
130
  f.write(content)
128
131
 
129
- def _generate_unique_filename(self, file_path: Path) -> Path:
130
- r"""Generate a unique filename if the target file already exists.
132
+ def _create_backup(self, file_path: Path) -> Optional[Path]:
133
+ r"""Create a backup of the file if it exists and backup is enabled.
131
134
 
132
135
  Args:
133
- file_path (Path): The original file path.
136
+ file_path (Path): The file path to backup.
134
137
 
135
138
  Returns:
136
- Path: A unique file path that doesn't exist yet.
139
+ Optional[Path]: Path to the backup file if created, None otherwise.
137
140
  """
138
- if not file_path.exists():
139
- return file_path
141
+ if not self.backup_enabled or not file_path.exists():
142
+ return None
140
143
 
141
- # Generate unique filename with timestamp and counter
144
+ # Generate backup filename with .bak extension and timestamp
142
145
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
143
- stem = file_path.stem
144
- suffix = file_path.suffix
145
- parent = file_path.parent
146
-
147
- # First try with timestamp
148
- new_path = parent / f"{stem}_{timestamp}{suffix}"
149
- if not new_path.exists():
150
- return new_path
151
-
152
- # If timestamp version exists, add counter
153
- counter = 1
154
- while True:
155
- new_path = parent / f"{stem}_{timestamp}_{counter}{suffix}"
156
- if not new_path.exists():
157
- return new_path
158
- counter += 1
146
+ backup_path = file_path.parent / f"{file_path.name}.{timestamp}.bak"
147
+
148
+ # Copy the file to backup location
149
+ import shutil
150
+
151
+ try:
152
+ shutil.copy2(file_path, backup_path)
153
+ logger.info(f"Created backup: {backup_path}")
154
+ return backup_path
155
+ except Exception as e:
156
+ logger.warning(f"Failed to create backup: {e}")
157
+ return None
159
158
 
160
159
  def _write_docx_file(self, file_path: Path, content: str) -> None:
161
160
  r"""Write text content to a DOCX file with default formatting.
@@ -1006,8 +1005,9 @@ class FileWriteToolkit(BaseToolkit):
1006
1005
  file_path = self._resolve_filepath(filename)
1007
1006
  file_path.parent.mkdir(parents=True, exist_ok=True)
1008
1007
 
1009
- # Generate unique filename if file exists
1010
- file_path = self._generate_unique_filename(file_path)
1008
+ # Create backup of existing file if backup is enabled
1009
+ if file_path.exists() and self.backup_enabled:
1010
+ self._create_backup(file_path)
1011
1011
 
1012
1012
  extension = file_path.suffix.lower()
1013
1013
 
@@ -1062,6 +1062,144 @@ class FileWriteToolkit(BaseToolkit):
1062
1062
  logger.error(error_msg)
1063
1063
  return error_msg
1064
1064
 
1065
+ # ----------------------------------------------
1066
+ # Read File Functions
1067
+ # ----------------------------------------------
1068
+ def read_file(
1069
+ self, file_paths: Union[str, List[str]]
1070
+ ) -> Union[str, Dict[str, str]]:
1071
+ r"""Read and return content of one or more files using MarkItDown
1072
+ for better format support.
1073
+
1074
+ This method uses MarkItDownLoader to convert various file formats
1075
+ to Markdown. It supports a wide range of formats including:
1076
+ - PDF (.pdf)
1077
+ - Microsoft Office: Word (.doc, .docx), Excel (.xls, .xlsx),
1078
+ PowerPoint (.ppt, .pptx)
1079
+ - EPUB (.epub)
1080
+ - HTML (.html, .htm)
1081
+ - Images (.jpg, .jpeg, .png) for OCR
1082
+ - Audio (.mp3, .wav) for transcription
1083
+ - Text-based formats (.csv, .json, .xml, .txt, .md)
1084
+ - ZIP archives (.zip)
1085
+
1086
+ Args:
1087
+ file_paths (Union[str, List[str]]): A single file path or a list
1088
+ of file paths to read. Paths can be relative or absolute.
1089
+ If relative, they will be resolved relative to the working
1090
+ directory.
1091
+
1092
+ Returns:
1093
+ Union[str, Dict[str, str]]:
1094
+ - If a single file path is provided: Returns the content as
1095
+ a string.
1096
+ - If multiple file paths are provided: Returns a dictionary
1097
+ where keys are file paths and values are the corresponding
1098
+ content in Markdown format.
1099
+ If conversion fails, returns an error message.
1100
+ """
1101
+ from camel.loaders.markitdown import MarkItDownLoader
1102
+
1103
+ try:
1104
+ # Handle single file path for backward compatibility
1105
+ if isinstance(file_paths, str):
1106
+ resolved_path = self._resolve_filepath(file_paths)
1107
+
1108
+ # Use MarkItDownLoader to convert the file
1109
+ result = MarkItDownLoader().convert_files(
1110
+ file_paths=[str(resolved_path)], parallel=False
1111
+ )
1112
+
1113
+ # Return the converted content or error message
1114
+ return result.get(
1115
+ str(resolved_path), f"Failed to read file: {resolved_path}"
1116
+ )
1117
+
1118
+ # Handle multiple file paths
1119
+ else:
1120
+ resolved_paths = [
1121
+ str(self._resolve_filepath(fp)) for fp in file_paths
1122
+ ]
1123
+
1124
+ # Use MarkItDownLoader to convert files in parallel
1125
+ result = MarkItDownLoader().convert_files(
1126
+ file_paths=resolved_paths, parallel=True
1127
+ )
1128
+
1129
+ # Map back to original paths if needed
1130
+ return_dict = {}
1131
+ for original, resolved in zip(file_paths, resolved_paths):
1132
+ return_dict[original] = result.get(
1133
+ resolved, f"Failed to read file: {resolved}"
1134
+ )
1135
+
1136
+ return return_dict
1137
+
1138
+ except Exception as e:
1139
+ return f"Error reading file(s): {e}"
1140
+
1141
+ # ----------------------------------------------
1142
+ # Edit File Functions
1143
+ # ----------------------------------------------
1144
+ def edit_file(
1145
+ self, file_path: str, old_content: str, new_content: str
1146
+ ) -> str:
1147
+ r"""Edit a file by replacing specified content.
1148
+
1149
+ This method performs simple text replacement in files. It reads
1150
+ the file, replaces all occurrences of old_content with new_content,
1151
+ and writes the result back.
1152
+
1153
+ Args:
1154
+ file_path (str): The path to the file to edit. Can be
1155
+ relative or absolute. If relative, it will be resolved
1156
+ relative to the working directory.
1157
+ old_content (str): The exact text to find and replace.
1158
+ new_content (str): The text to replace old_content with.
1159
+
1160
+ Returns:
1161
+ str: A success message if the edit was successful, or an
1162
+ error message if the content wasn't found or an error occurred.
1163
+ """
1164
+ try:
1165
+ working_path = self._resolve_filepath(file_path)
1166
+
1167
+ if not working_path.exists():
1168
+ return f"Error: File {working_path} does not exist"
1169
+
1170
+ # Create backup before editing if enabled
1171
+ self._create_backup(working_path)
1172
+
1173
+ # Read the file content
1174
+ try:
1175
+ file_text = working_path.read_text(
1176
+ encoding=self.default_encoding
1177
+ )
1178
+ except Exception as e:
1179
+ return f"Error reading file: {e}"
1180
+
1181
+ # Check if the old_content exists in the file
1182
+ if old_content not in file_text:
1183
+ return (
1184
+ f"No replacement performed: '{old_content}' not found in "
1185
+ f"{working_path}."
1186
+ )
1187
+
1188
+ # Replace the content
1189
+ new_file_text = file_text.replace(old_content, new_content)
1190
+
1191
+ # Write back to file
1192
+ try:
1193
+ working_path.write_text(
1194
+ new_file_text, encoding=self.default_encoding
1195
+ )
1196
+ return f"Successfully edited {working_path}"
1197
+ except Exception as e:
1198
+ return f"Error writing file: {e}"
1199
+
1200
+ except Exception as e:
1201
+ return f"Error editing file: {e}"
1202
+
1065
1203
  def get_tools(self) -> List[FunctionTool]:
1066
1204
  r"""Return a list of FunctionTool objects representing the functions
1067
1205
  in the toolkit.
@@ -1072,4 +1210,26 @@ class FileWriteToolkit(BaseToolkit):
1072
1210
  """
1073
1211
  return [
1074
1212
  FunctionTool(self.write_to_file),
1213
+ FunctionTool(self.read_file),
1214
+ FunctionTool(self.edit_file),
1075
1215
  ]
1216
+
1217
+
1218
+ # Backward compatibility: FileWriteToolkit as deprecated alias
1219
+ class FileWriteToolkit(FileToolkit):
1220
+ r"""Deprecated: Use FileToolkit instead.
1221
+
1222
+ This class is maintained for backward compatibility only.
1223
+ Please use FileToolkit for new code.
1224
+ """
1225
+
1226
+ def __init__(self, *args, **kwargs):
1227
+ import warnings
1228
+
1229
+ warnings.warn(
1230
+ "FileWriteToolkit is deprecated and will be removed in a "
1231
+ "future version. Please use FileToolkit instead.",
1232
+ DeprecationWarning,
1233
+ stacklevel=2,
1234
+ )
1235
+ super().__init__(*args, **kwargs)
@@ -156,7 +156,12 @@ def get_openai_tool_schema(func: Callable) -> Dict[str, Any]:
156
156
  if (name := param.arg_name) in parameters_dict["properties"] and (
157
157
  description := param.description
158
158
  ):
159
- parameters_dict["properties"][name]["description"] = description
159
+ # OpenAI does not allow descriptions on properties that use $ref.
160
+ # To avoid schema errors, we only add the description if "$ref" is
161
+ # not present.
162
+ prop = parameters_dict["properties"][name]
163
+ if "$ref" not in prop:
164
+ prop["description"] = description
160
165
 
161
166
  short_description = docstring.short_description or ""
162
167
  long_description = docstring.long_description or ""
@@ -12,14 +12,12 @@
12
12
  # limitations under the License.
13
13
  # ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
14
14
 
15
- from typing import List, Optional
16
-
17
- from camel.toolkits import BaseToolkit, FunctionTool
15
+ from typing import Optional
18
16
 
19
17
  from .mcp_toolkit import MCPToolkit
20
18
 
21
19
 
22
- class GoogleDriveMCPToolkit(BaseToolkit):
20
+ class GoogleDriveMCPToolkit(MCPToolkit):
23
21
  r"""GoogleDriveMCPToolkit provides an interface for interacting with
24
22
  Google Drive using the Google Drive MCP server.
25
23
 
@@ -41,33 +39,16 @@ class GoogleDriveMCPToolkit(BaseToolkit):
41
39
  credentials_path (Optional[str]): Path to the Google Drive
42
40
  credentials file. (default: :obj:`None`)
43
41
  """
44
- super().__init__(timeout=timeout)
45
42
 
46
- self._mcp_toolkit = MCPToolkit(
47
- config_dict={
48
- "mcpServers": {
49
- "gdrive": {
50
- "command": "npx",
51
- "args": ["-y", "@modelcontextprotocol/server-gdrive"],
52
- "env": {"GDRIVE_CREDENTIALS_PATH": credentials_path},
53
- }
43
+ config_dict = {
44
+ "mcpServers": {
45
+ "gdrive": {
46
+ "command": "npx",
47
+ "args": ["-y", "@modelcontextprotocol/server-gdrive"],
48
+ "env": {"GDRIVE_CREDENTIALS_PATH": credentials_path},
54
49
  }
55
- },
56
- timeout=timeout,
57
- )
58
-
59
- async def connect(self):
60
- r"""Explicitly connect to the Google Drive MCP server."""
61
- await self._mcp_toolkit.connect()
62
-
63
- async def disconnect(self):
64
- r"""Explicitly disconnect from the Google Drive MCP server."""
65
- await self._mcp_toolkit.disconnect()
50
+ }
51
+ }
66
52
 
67
- def get_tools(self) -> List[FunctionTool]:
68
- r"""Returns a list of tools provided by the GoogleDriveMCPToolkit.
69
-
70
- Returns:
71
- List[FunctionTool]: List of available tools.
72
- """
73
- return self._mcp_toolkit.get_tools()
53
+ # Initialize parent MCPToolkit with Playwright configuration
54
+ super().__init__(config_dict=config_dict, timeout=timeout)
@@ -43,6 +43,10 @@ class BrowserConfig:
43
43
  # CDP connection configuration
44
44
  connect_over_cdp: bool = False
45
45
  cdp_url: Optional[str] = None
46
+ cdp_keep_current_page: bool = False
47
+
48
+ # Full visual mode configuration
49
+ full_visual_mode: bool = False
46
50
 
47
51
 
48
52
  @dataclass
@@ -51,6 +55,7 @@ class ToolkitConfig:
51
55
 
52
56
  cache_dir: str = "tmp/"
53
57
  browser_log_to_file: bool = False
58
+ log_dir: Optional[str] = None
54
59
  session_id: Optional[str] = None
55
60
  enabled_tools: Optional[list] = None
56
61
 
@@ -106,6 +111,8 @@ class ConfigLoader:
106
111
  browser_kwargs["connect_over_cdp"] = value
107
112
  elif key == "cdpUrl":
108
113
  browser_kwargs["cdp_url"] = value
114
+ elif key == "cdpKeepCurrentPage":
115
+ browser_kwargs["cdp_keep_current_page"] = value
109
116
  elif key == "consoleLogLimit":
110
117
  browser_kwargs["console_log_limit"] = value
111
118
  elif key == "cacheDir":
@@ -116,6 +123,8 @@ class ConfigLoader:
116
123
  toolkit_kwargs["session_id"] = value
117
124
  elif key == "enabledTools":
118
125
  toolkit_kwargs["enabled_tools"] = value
126
+ elif key == "fullVisualMode":
127
+ browser_kwargs["full_visual_mode"] = value
119
128
 
120
129
  browser_config = BrowserConfig(**browser_kwargs)
121
130
  toolkit_config = ToolkitConfig(**toolkit_kwargs)
@@ -142,10 +151,13 @@ class ConfigLoader:
142
151
  "screenshotTimeout": self.browser_config.screenshot_timeout,
143
152
  "pageStabilityTimeout": self.browser_config.page_stability_timeout,
144
153
  "browser_log_to_file": self.toolkit_config.browser_log_to_file,
154
+ "log_dir": self.toolkit_config.log_dir,
145
155
  "session_id": self.toolkit_config.session_id,
146
156
  "viewport_limit": self.browser_config.viewport_limit,
147
157
  "connectOverCdp": self.browser_config.connect_over_cdp,
148
158
  "cdpUrl": self.browser_config.cdp_url,
159
+ "cdpKeepCurrentPage": self.browser_config.cdp_keep_current_page,
160
+ "fullVisualMode": self.browser_config.full_visual_mode,
149
161
  }
150
162
 
151
163
  def get_timeout_config(self) -> Dict[str, Optional[int]]:
@@ -25,6 +25,66 @@ class HybridBrowserToolkit(BaseToolkit):
25
25
  This wrapper allows users to choose between:
26
26
  - 'typescript': WebSocket-based implementation using TypeScript/Node.js
27
27
  - 'python': Pure Python implementation using Playwright directly
28
+
29
+ Args:
30
+ mode (Literal["typescript", "python"]): Implementation mode. -
31
+ 'typescript': Uses WebSocket-based TypeScript implementation -
32
+ 'python': Uses pure Python Playwright implementation. Defaults to
33
+ "typescript".
34
+ headless (bool): Whether to run browser in headless mode.
35
+ Defaults to True.
36
+ user_data_dir (Optional[str]): Directory for user data
37
+ persistence. Defaults to None.
38
+ stealth (bool): Whether to enable stealth mode. Defaults to
39
+ False.
40
+ web_agent_model (Optional[BaseModelBackend]): Model for web
41
+ agent operations. Defaults to None.
42
+ cache_dir (str): Directory for caching. Defaults to "tmp/".
43
+ enabled_tools (Optional[List[str]]): List of enabled tools.
44
+ Defaults to None.
45
+ browser_log_to_file (bool): Whether to log browser actions to
46
+ file. Defaults to False.
47
+ log_dir (Optional[str]): Custom directory path for log files.
48
+ If None, defaults to "browser_log". Defaults to None.
49
+ session_id (Optional[str]): Session identifier. Defaults to None.
50
+ default_start_url (str): Default URL to start with. Defaults
51
+ to "https://google.com/".
52
+ default_timeout (Optional[int]): Default timeout in
53
+ milliseconds. Defaults to None.
54
+ short_timeout (Optional[int]): Short timeout in milliseconds.
55
+ Defaults to None.
56
+ navigation_timeout (Optional[int]): Navigation timeout in
57
+ milliseconds. Defaults to None.
58
+ network_idle_timeout (Optional[int]): Network idle timeout in
59
+ milliseconds. Defaults to None.
60
+ screenshot_timeout (Optional[int]): Screenshot timeout in
61
+ milliseconds. Defaults to None.
62
+ page_stability_timeout (Optional[int]): Page stability timeout
63
+ in milliseconds. Defaults to None.
64
+ dom_content_loaded_timeout (Optional[int]): DOM content loaded
65
+ timeout in milliseconds. Defaults to None.
66
+ viewport_limit (bool): Whether to filter page snapshot
67
+ elements to only those visible in the current viewport.
68
+ Defaults to False.
69
+ connect_over_cdp (bool): Whether to connect to an existing
70
+ browser via Chrome DevTools Protocol. Defaults to False.
71
+ (Only supported in TypeScript mode)
72
+ cdp_url (Optional[str]): WebSocket endpoint URL for CDP
73
+ connection. Required when connect_over_cdp is True.
74
+ Defaults to None. (Only supported in TypeScript mode)
75
+ cdp_keep_current_page (bool): When True and using CDP mode,
76
+ won't create new pages but use the existing one. Defaults to False.
77
+ (Only supported in TypeScript mode)
78
+ full_visual_mode (bool): When True, browser actions like click,
79
+ browser_open, visit_page, etc. will return 'full visual mode'
80
+ as snapshot instead of actual page content. The
81
+ browser_get_page_snapshot method will still return the actual
82
+ snapshot. Defaults to False.
83
+ **kwargs: Additional keyword arguments passed to the
84
+ implementation.
85
+
86
+ Returns:
87
+ HybridBrowserToolkit instance of the specified implementation.
28
88
  """
29
89
 
30
90
  def __new__(
@@ -35,11 +95,12 @@ class HybridBrowserToolkit(BaseToolkit):
35
95
  user_data_dir: Optional[str] = None,
36
96
  stealth: bool = False,
37
97
  web_agent_model: Optional[BaseModelBackend] = None,
38
- cache_dir: str = "tmp/",
98
+ cache_dir: Optional[str] = None,
39
99
  enabled_tools: Optional[List[str]] = None,
40
100
  browser_log_to_file: bool = False,
101
+ log_dir: Optional[str] = None,
41
102
  session_id: Optional[str] = None,
42
- default_start_url: str = "https://google.com/",
103
+ default_start_url: Optional[str] = None,
43
104
  default_timeout: Optional[int] = None,
44
105
  short_timeout: Optional[int] = None,
45
106
  navigation_timeout: Optional[int] = None,
@@ -50,6 +111,8 @@ class HybridBrowserToolkit(BaseToolkit):
50
111
  viewport_limit: bool = False,
51
112
  connect_over_cdp: bool = False,
52
113
  cdp_url: Optional[str] = None,
114
+ cdp_keep_current_page: bool = False,
115
+ full_visual_mode: bool = False,
53
116
  **kwargs: Any,
54
117
  ) -> Any:
55
118
  r"""Create a HybridBrowserToolkit instance with the specified mode.
@@ -72,6 +135,8 @@ class HybridBrowserToolkit(BaseToolkit):
72
135
  Defaults to None.
73
136
  browser_log_to_file (bool): Whether to log browser actions to
74
137
  file. Defaults to False.
138
+ log_dir (Optional[str]): Custom directory path for log files.
139
+ If None, defaults to "browser_log". Defaults to None.
75
140
  session_id (Optional[str]): Session identifier. Defaults to None.
76
141
  default_start_url (str): Default URL to start with. Defaults
77
142
  to "https://google.com/".
@@ -98,6 +163,14 @@ class HybridBrowserToolkit(BaseToolkit):
98
163
  cdp_url (Optional[str]): WebSocket endpoint URL for CDP
99
164
  connection. Required when connect_over_cdp is True.
100
165
  Defaults to None. (Only supported in TypeScript mode)
166
+ cdp_keep_current_page (bool): When True and using CDP mode,
167
+ won't create new pages but use the existing one. Defaults to False.
168
+ (Only supported in TypeScript mode)
169
+ full_visual_mode (bool): When True, browser actions like click,
170
+ browser_open, visit_page, etc. will return 'full visual mode'
171
+ as snapshot instead of actual page content. The
172
+ browser_get_page_snapshot method will still return the actual
173
+ snapshot. Defaults to False.
101
174
  **kwargs: Additional keyword arguments passed to the
102
175
  implementation.
103
176
 
@@ -117,6 +190,7 @@ class HybridBrowserToolkit(BaseToolkit):
117
190
  cache_dir=cache_dir,
118
191
  enabled_tools=enabled_tools,
119
192
  browser_log_to_file=browser_log_to_file,
193
+ log_dir=log_dir,
120
194
  session_id=session_id,
121
195
  default_start_url=default_start_url,
122
196
  default_timeout=default_timeout,
@@ -129,6 +203,8 @@ class HybridBrowserToolkit(BaseToolkit):
129
203
  viewport_limit=viewport_limit,
130
204
  connect_over_cdp=connect_over_cdp,
131
205
  cdp_url=cdp_url,
206
+ cdp_keep_current_page=cdp_keep_current_page,
207
+ full_visual_mode=full_visual_mode,
132
208
  **kwargs,
133
209
  )
134
210
  elif mode == "python":
@@ -160,6 +236,7 @@ class HybridBrowserToolkit(BaseToolkit):
160
236
  cache_dir=cache_dir,
161
237
  enabled_tools=enabled_tools,
162
238
  browser_log_to_file=browser_log_to_file,
239
+ log_dir=log_dir,
163
240
  session_id=session_id,
164
241
  default_start_url=default_start_url,
165
242
  default_timeout=default_timeout,