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.
- camel/__init__.py +1 -1
- camel/agents/chat_agent.py +1001 -205
- camel/agents/mcp_agent.py +30 -27
- camel/configs/__init__.py +6 -0
- camel/configs/amd_config.py +70 -0
- camel/configs/cometapi_config.py +104 -0
- camel/data_collectors/alpaca_collector.py +15 -6
- camel/environments/tic_tac_toe.py +1 -1
- camel/interpreters/__init__.py +2 -0
- camel/interpreters/docker/Dockerfile +3 -12
- camel/interpreters/microsandbox_interpreter.py +395 -0
- camel/loaders/__init__.py +11 -2
- camel/loaders/chunkr_reader.py +9 -0
- camel/memories/__init__.py +2 -1
- camel/memories/agent_memories.py +3 -1
- camel/memories/blocks/chat_history_block.py +21 -3
- camel/memories/records.py +88 -8
- camel/messages/base.py +127 -34
- camel/models/__init__.py +4 -0
- camel/models/amd_model.py +101 -0
- camel/models/azure_openai_model.py +0 -6
- camel/models/base_model.py +30 -0
- camel/models/cometapi_model.py +83 -0
- camel/models/model_factory.py +4 -0
- camel/models/openai_compatible_model.py +0 -6
- camel/models/openai_model.py +0 -6
- camel/models/zhipuai_model.py +61 -2
- camel/parsers/__init__.py +18 -0
- camel/parsers/mcp_tool_call_parser.py +176 -0
- camel/retrievers/auto_retriever.py +1 -0
- camel/runtimes/daytona_runtime.py +11 -12
- camel/societies/workforce/prompts.py +131 -50
- camel/societies/workforce/single_agent_worker.py +434 -49
- camel/societies/workforce/structured_output_handler.py +30 -18
- camel/societies/workforce/task_channel.py +43 -0
- camel/societies/workforce/utils.py +105 -12
- camel/societies/workforce/workforce.py +1322 -311
- camel/societies/workforce/workforce_logger.py +24 -5
- camel/storages/key_value_storages/json.py +15 -2
- camel/storages/object_storages/google_cloud.py +1 -1
- camel/storages/vectordb_storages/oceanbase.py +10 -11
- camel/storages/vectordb_storages/tidb.py +8 -6
- camel/tasks/task.py +4 -3
- camel/toolkits/__init__.py +18 -5
- camel/toolkits/aci_toolkit.py +45 -0
- camel/toolkits/code_execution.py +28 -1
- camel/toolkits/context_summarizer_toolkit.py +684 -0
- camel/toolkits/dingtalk.py +1135 -0
- camel/toolkits/edgeone_pages_mcp_toolkit.py +11 -31
- camel/toolkits/{file_write_toolkit.py → file_toolkit.py} +194 -34
- camel/toolkits/function_tool.py +6 -1
- camel/toolkits/google_drive_mcp_toolkit.py +12 -31
- camel/toolkits/hybrid_browser_toolkit/config_loader.py +12 -0
- camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit.py +79 -2
- camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit_ts.py +95 -59
- camel/toolkits/hybrid_browser_toolkit/installer.py +203 -0
- camel/toolkits/hybrid_browser_toolkit/ts/package-lock.json +5 -612
- camel/toolkits/hybrid_browser_toolkit/ts/package.json +0 -1
- camel/toolkits/hybrid_browser_toolkit/ts/src/browser-session.ts +619 -95
- camel/toolkits/hybrid_browser_toolkit/ts/src/config-loader.ts +7 -2
- camel/toolkits/hybrid_browser_toolkit/ts/src/hybrid-browser-toolkit.ts +115 -219
- camel/toolkits/hybrid_browser_toolkit/ts/src/parent-child-filter.ts +226 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/snapshot-parser.ts +219 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/som-screenshot-injected.ts +543 -0
- camel/toolkits/hybrid_browser_toolkit/ts/src/types.ts +1 -0
- camel/toolkits/hybrid_browser_toolkit/ts/websocket-server.js +39 -6
- camel/toolkits/hybrid_browser_toolkit/ws_wrapper.py +405 -131
- camel/toolkits/hybrid_browser_toolkit_py/hybrid_browser_toolkit.py +9 -5
- camel/toolkits/{openai_image_toolkit.py → image_generation_toolkit.py} +98 -31
- camel/toolkits/markitdown_toolkit.py +27 -1
- camel/toolkits/mcp_toolkit.py +348 -348
- camel/toolkits/message_integration.py +3 -0
- camel/toolkits/minimax_mcp_toolkit.py +195 -0
- camel/toolkits/note_taking_toolkit.py +18 -8
- camel/toolkits/notion_mcp_toolkit.py +16 -26
- camel/toolkits/origene_mcp_toolkit.py +8 -49
- camel/toolkits/playwright_mcp_toolkit.py +12 -31
- camel/toolkits/resend_toolkit.py +168 -0
- camel/toolkits/slack_toolkit.py +50 -1
- camel/toolkits/terminal_toolkit/__init__.py +18 -0
- camel/toolkits/terminal_toolkit/terminal_toolkit.py +924 -0
- camel/toolkits/terminal_toolkit/utils.py +532 -0
- camel/toolkits/vertex_ai_veo_toolkit.py +590 -0
- camel/toolkits/video_analysis_toolkit.py +17 -11
- camel/toolkits/wechat_official_toolkit.py +483 -0
- camel/types/enums.py +124 -1
- camel/types/unified_model_type.py +5 -0
- camel/utils/commons.py +17 -0
- camel/utils/context_utils.py +804 -0
- camel/utils/mcp.py +136 -2
- camel/utils/token_counting.py +25 -17
- {camel_ai-0.2.75a6.dist-info → camel_ai-0.2.76.dist-info}/METADATA +158 -59
- {camel_ai-0.2.75a6.dist-info → camel_ai-0.2.76.dist-info}/RECORD +95 -76
- camel/loaders/pandas_reader.py +0 -368
- camel/toolkits/terminal_toolkit.py +0 -1788
- {camel_ai-0.2.75a6.dist-info → camel_ai-0.2.76.dist-info}/WHEEL +0 -0
- {camel_ai-0.2.75a6.dist-info → camel_ai-0.2.76.dist-info}/licenses/LICENSE +0 -0
|
@@ -37,7 +37,6 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
37
37
|
_snapshotForAI functionality for enhanced AI integration.
|
|
38
38
|
"""
|
|
39
39
|
|
|
40
|
-
# Default tool list - core browser functionality
|
|
41
40
|
DEFAULT_TOOLS: ClassVar[List[str]] = [
|
|
42
41
|
"browser_open",
|
|
43
42
|
"browser_close",
|
|
@@ -49,7 +48,6 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
49
48
|
"browser_switch_tab",
|
|
50
49
|
]
|
|
51
50
|
|
|
52
|
-
# All available tools
|
|
53
51
|
ALL_TOOLS: ClassVar[List[str]] = [
|
|
54
52
|
"browser_open",
|
|
55
53
|
"browser_close",
|
|
@@ -83,11 +81,12 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
83
81
|
user_data_dir: Optional[str] = None,
|
|
84
82
|
stealth: bool = False,
|
|
85
83
|
web_agent_model: Optional[BaseModelBackend] = None,
|
|
86
|
-
cache_dir: str =
|
|
84
|
+
cache_dir: Optional[str] = None,
|
|
87
85
|
enabled_tools: Optional[List[str]] = None,
|
|
88
86
|
browser_log_to_file: bool = False,
|
|
87
|
+
log_dir: Optional[str] = None,
|
|
89
88
|
session_id: Optional[str] = None,
|
|
90
|
-
default_start_url: str =
|
|
89
|
+
default_start_url: Optional[str] = None,
|
|
91
90
|
default_timeout: Optional[int] = None,
|
|
92
91
|
short_timeout: Optional[int] = None,
|
|
93
92
|
navigation_timeout: Optional[int] = None,
|
|
@@ -98,6 +97,8 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
98
97
|
viewport_limit: bool = False,
|
|
99
98
|
connect_over_cdp: bool = False,
|
|
100
99
|
cdp_url: Optional[str] = None,
|
|
100
|
+
cdp_keep_current_page: bool = False,
|
|
101
|
+
full_visual_mode: bool = False,
|
|
101
102
|
) -> None:
|
|
102
103
|
r"""Initialize the HybridBrowserToolkit.
|
|
103
104
|
|
|
@@ -115,6 +116,8 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
115
116
|
Defaults to None.
|
|
116
117
|
browser_log_to_file (bool): Whether to log browser actions to
|
|
117
118
|
file. Defaults to False.
|
|
119
|
+
log_dir (Optional[str]): Custom directory path for log files.
|
|
120
|
+
If None, defaults to "browser_log". Defaults to None.
|
|
118
121
|
session_id (Optional[str]): Session identifier. Defaults to None.
|
|
119
122
|
default_start_url (str): Default URL to start with. Defaults
|
|
120
123
|
to "https://google.com/".
|
|
@@ -143,11 +146,15 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
143
146
|
cdp_url (Optional[str]): WebSocket endpoint URL for CDP
|
|
144
147
|
connection (e.g., 'ws://localhost:9222/devtools/browser/...').
|
|
145
148
|
Required when connect_over_cdp is True. Defaults to None.
|
|
149
|
+
cdp_keep_current_page (bool): When True and using CDP mode,
|
|
150
|
+
won't create new pages but use the existing one. Defaults to False.
|
|
151
|
+
full_visual_mode (bool): When True, browser actions like click,
|
|
152
|
+
browser_open, visit_page, etc. will not return snapshots.
|
|
153
|
+
Defaults to False.
|
|
146
154
|
"""
|
|
147
155
|
super().__init__()
|
|
148
156
|
RegisteredAgentToolkit.__init__(self)
|
|
149
157
|
|
|
150
|
-
# Initialize configuration loader
|
|
151
158
|
self.config_loader = ConfigLoader.from_kwargs(
|
|
152
159
|
headless=headless,
|
|
153
160
|
user_data_dir=user_data_dir,
|
|
@@ -163,16 +170,29 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
163
170
|
viewport_limit=viewport_limit,
|
|
164
171
|
cache_dir=cache_dir,
|
|
165
172
|
browser_log_to_file=browser_log_to_file,
|
|
173
|
+
log_dir=log_dir,
|
|
166
174
|
session_id=session_id,
|
|
167
175
|
enabled_tools=enabled_tools,
|
|
168
176
|
connect_over_cdp=connect_over_cdp,
|
|
169
177
|
cdp_url=cdp_url,
|
|
178
|
+
cdp_keep_current_page=cdp_keep_current_page,
|
|
179
|
+
full_visual_mode=full_visual_mode,
|
|
170
180
|
)
|
|
171
181
|
|
|
172
|
-
# Legacy attribute access for backward compatibility
|
|
173
182
|
browser_config = self.config_loader.get_browser_config()
|
|
174
183
|
toolkit_config = self.config_loader.get_toolkit_config()
|
|
175
184
|
|
|
185
|
+
if (
|
|
186
|
+
browser_config.cdp_keep_current_page
|
|
187
|
+
and default_start_url is not None
|
|
188
|
+
):
|
|
189
|
+
raise ValueError(
|
|
190
|
+
"Cannot use default_start_url with "
|
|
191
|
+
"cdp_keep_current_page=True. When cdp_keep_current_page "
|
|
192
|
+
"is True, the browser will keep the current page and not "
|
|
193
|
+
"navigate to any URL."
|
|
194
|
+
)
|
|
195
|
+
|
|
176
196
|
self._headless = browser_config.headless
|
|
177
197
|
self._user_data_dir = browser_config.user_data_dir
|
|
178
198
|
self._stealth = browser_config.stealth
|
|
@@ -182,8 +202,8 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
182
202
|
self._default_start_url = browser_config.default_start_url
|
|
183
203
|
self._session_id = toolkit_config.session_id or "default"
|
|
184
204
|
self._viewport_limit = browser_config.viewport_limit
|
|
205
|
+
self._full_visual_mode = browser_config.full_visual_mode
|
|
185
206
|
|
|
186
|
-
# Store timeout configuration for backward compatibility
|
|
187
207
|
self._default_timeout = browser_config.default_timeout
|
|
188
208
|
self._short_timeout = browser_config.short_timeout
|
|
189
209
|
self._navigation_timeout = browser_config.navigation_timeout
|
|
@@ -194,11 +214,9 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
194
214
|
browser_config.dom_content_loaded_timeout
|
|
195
215
|
)
|
|
196
216
|
|
|
197
|
-
# Configure enabled tools
|
|
198
217
|
if enabled_tools is None:
|
|
199
218
|
self.enabled_tools = self.DEFAULT_TOOLS.copy()
|
|
200
219
|
else:
|
|
201
|
-
# Validate enabled tools
|
|
202
220
|
invalid_tools = [
|
|
203
221
|
tool for tool in enabled_tools if tool not in self.ALL_TOOLS
|
|
204
222
|
]
|
|
@@ -211,7 +229,6 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
211
229
|
|
|
212
230
|
logger.info(f"Enabled tools: {self.enabled_tools}")
|
|
213
231
|
|
|
214
|
-
# Initialize WebSocket wrapper
|
|
215
232
|
self._ws_wrapper: Optional[WebSocketBrowserWrapper] = None
|
|
216
233
|
self._ws_config = self.config_loader.to_ws_config()
|
|
217
234
|
|
|
@@ -238,13 +255,29 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
238
255
|
|
|
239
256
|
import asyncio
|
|
240
257
|
|
|
258
|
+
is_cdp = (
|
|
259
|
+
self._ws_config.get('connectOverCdp', False)
|
|
260
|
+
if hasattr(self, '_ws_config')
|
|
261
|
+
else False
|
|
262
|
+
)
|
|
263
|
+
|
|
241
264
|
try:
|
|
242
265
|
loop = asyncio.get_event_loop()
|
|
243
266
|
if not loop.is_closed() and not loop.is_running():
|
|
244
267
|
try:
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
268
|
+
if is_cdp:
|
|
269
|
+
# CDP: disconnect only
|
|
270
|
+
loop.run_until_complete(
|
|
271
|
+
asyncio.wait_for(
|
|
272
|
+
self.disconnect_websocket(), timeout=2.0
|
|
273
|
+
)
|
|
274
|
+
)
|
|
275
|
+
else:
|
|
276
|
+
loop.run_until_complete(
|
|
277
|
+
asyncio.wait_for(
|
|
278
|
+
self.browser_close(), timeout=2.0
|
|
279
|
+
)
|
|
280
|
+
)
|
|
248
281
|
except asyncio.TimeoutError:
|
|
249
282
|
pass
|
|
250
283
|
except (RuntimeError, ImportError):
|
|
@@ -267,8 +300,6 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
267
300
|
"""Get the cache directory."""
|
|
268
301
|
return self._cache_dir
|
|
269
302
|
|
|
270
|
-
# Public API Methods
|
|
271
|
-
|
|
272
303
|
async def browser_open(self) -> Dict[str, Any]:
|
|
273
304
|
r"""Starts a new browser session. This must be the first browser
|
|
274
305
|
action.
|
|
@@ -289,7 +320,6 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
289
320
|
ws_wrapper = await self._get_ws_wrapper()
|
|
290
321
|
result = await ws_wrapper.open_browser(self._default_start_url)
|
|
291
322
|
|
|
292
|
-
# Add tab information
|
|
293
323
|
tab_info = await ws_wrapper.get_tab_info()
|
|
294
324
|
result.update(
|
|
295
325
|
{
|
|
@@ -334,6 +364,31 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
334
364
|
logger.error(f"Failed to close browser: {e}")
|
|
335
365
|
return f"Error closing browser: {e}"
|
|
336
366
|
|
|
367
|
+
async def disconnect_websocket(self) -> str:
|
|
368
|
+
r"""Disconnects the WebSocket connection without closing the browser.
|
|
369
|
+
|
|
370
|
+
This is useful when using CDP mode where the browser should
|
|
371
|
+
remain open.
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
str: A confirmation message.
|
|
375
|
+
"""
|
|
376
|
+
try:
|
|
377
|
+
if self._ws_wrapper:
|
|
378
|
+
is_cdp = self._ws_config.get('connectOverCdp', False)
|
|
379
|
+
|
|
380
|
+
if is_cdp:
|
|
381
|
+
# CDP: disconnect only
|
|
382
|
+
await self._ws_wrapper.disconnect_only()
|
|
383
|
+
else:
|
|
384
|
+
await self._ws_wrapper.stop()
|
|
385
|
+
|
|
386
|
+
self._ws_wrapper = None
|
|
387
|
+
return "WebSocket disconnected."
|
|
388
|
+
except Exception as e:
|
|
389
|
+
logger.error(f"Failed to disconnect WebSocket: {e}")
|
|
390
|
+
return f"Error disconnecting WebSocket: {e}"
|
|
391
|
+
|
|
337
392
|
async def browser_visit_page(self, url: str) -> Dict[str, Any]:
|
|
338
393
|
r"""Opens a URL in a new browser tab and switches to it.
|
|
339
394
|
|
|
@@ -353,7 +408,6 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
353
408
|
ws_wrapper = await self._get_ws_wrapper()
|
|
354
409
|
result = await ws_wrapper.visit_page(url)
|
|
355
410
|
|
|
356
|
-
# Add tab information
|
|
357
411
|
tab_info = await ws_wrapper.get_tab_info()
|
|
358
412
|
result.update(
|
|
359
413
|
{
|
|
@@ -399,7 +453,6 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
399
453
|
ws_wrapper = await self._get_ws_wrapper()
|
|
400
454
|
result = await ws_wrapper.back()
|
|
401
455
|
|
|
402
|
-
# Add tab information
|
|
403
456
|
tab_info = await ws_wrapper.get_tab_info()
|
|
404
457
|
result.update(
|
|
405
458
|
{
|
|
@@ -445,7 +498,6 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
445
498
|
ws_wrapper = await self._get_ws_wrapper()
|
|
446
499
|
result = await ws_wrapper.forward()
|
|
447
500
|
|
|
448
|
-
# Add tab information
|
|
449
501
|
tab_info = await ws_wrapper.get_tab_info()
|
|
450
502
|
result.update(
|
|
451
503
|
{
|
|
@@ -537,19 +589,14 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
537
589
|
ws_wrapper = await self._get_ws_wrapper()
|
|
538
590
|
result = await ws_wrapper.get_som_screenshot()
|
|
539
591
|
|
|
540
|
-
# Initialize result text
|
|
541
592
|
result_text = result.text
|
|
542
593
|
file_path = None
|
|
543
594
|
|
|
544
|
-
# Save screenshot to cache directory if images are available
|
|
545
595
|
if result.images:
|
|
546
|
-
# Ensure cache directory exists (use absolute path)
|
|
547
596
|
cache_dir = os.path.abspath(self._cache_dir)
|
|
548
597
|
os.makedirs(cache_dir, exist_ok=True)
|
|
549
598
|
|
|
550
|
-
# Get current page URL for filename
|
|
551
599
|
try:
|
|
552
|
-
# Try to get the current page URL from the wrapper
|
|
553
600
|
page_info = await ws_wrapper.get_tab_info()
|
|
554
601
|
current_tab = next(
|
|
555
602
|
(tab for tab in page_info if tab.get('is_current')),
|
|
@@ -559,7 +606,6 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
559
606
|
except Exception:
|
|
560
607
|
url = 'unknown'
|
|
561
608
|
|
|
562
|
-
# Generate filename
|
|
563
609
|
parsed_url = urllib.parse.urlparse(url)
|
|
564
610
|
url_name = sanitize_filename(
|
|
565
611
|
str(parsed_url.path) or 'homepage', max_length=241
|
|
@@ -569,24 +615,19 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
569
615
|
cache_dir, f"{url_name}_{timestamp}_som.png"
|
|
570
616
|
)
|
|
571
617
|
|
|
572
|
-
# Extract base64 data and save to file
|
|
573
618
|
for _, image_data in enumerate(result.images):
|
|
574
619
|
if image_data.startswith('data:image/png;base64,'):
|
|
575
|
-
# Remove data URL prefix
|
|
576
620
|
base64_data = image_data.split(',', 1)[1]
|
|
577
621
|
|
|
578
|
-
# Decode and save
|
|
579
622
|
image_bytes = base64.b64decode(base64_data)
|
|
580
623
|
with open(file_path, 'wb') as f:
|
|
581
624
|
f.write(image_bytes)
|
|
582
625
|
|
|
583
626
|
logger.info(f"Screenshot saved to: {file_path}")
|
|
584
627
|
|
|
585
|
-
# Update result text to include file path
|
|
586
628
|
result_text += f" (saved to: {file_path})"
|
|
587
629
|
break
|
|
588
630
|
|
|
589
|
-
# Analyze image if requested and agent is registered
|
|
590
631
|
if read_image and file_path:
|
|
591
632
|
if self.agent is None:
|
|
592
633
|
logger.error(
|
|
@@ -601,7 +642,6 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
601
642
|
)
|
|
602
643
|
else:
|
|
603
644
|
try:
|
|
604
|
-
# Load the image and create a message
|
|
605
645
|
from PIL import Image
|
|
606
646
|
|
|
607
647
|
img = Image.open(file_path)
|
|
@@ -612,7 +652,6 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
612
652
|
image_list=[img],
|
|
613
653
|
)
|
|
614
654
|
|
|
615
|
-
# Get agent's analysis
|
|
616
655
|
response = await self.agent.astep(message)
|
|
617
656
|
agent_response = response.msgs[0].content
|
|
618
657
|
result_text += f". Agent analysis: {agent_response}"
|
|
@@ -646,24 +685,30 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
646
685
|
ws_wrapper = await self._get_ws_wrapper()
|
|
647
686
|
result = await ws_wrapper.click(ref)
|
|
648
687
|
|
|
649
|
-
# Add tab information
|
|
650
688
|
tab_info = await ws_wrapper.get_tab_info()
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
)
|
|
660
|
-
|
|
689
|
+
|
|
690
|
+
response = {
|
|
691
|
+
"result": result.get("result", ""),
|
|
692
|
+
"snapshot": result.get("snapshot", ""),
|
|
693
|
+
"tabs": tab_info,
|
|
694
|
+
"current_tab": next(
|
|
695
|
+
(
|
|
696
|
+
i
|
|
697
|
+
for i, tab in enumerate(tab_info)
|
|
698
|
+
if tab.get("is_current")
|
|
661
699
|
),
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
700
|
+
0,
|
|
701
|
+
),
|
|
702
|
+
"total_tabs": len(tab_info),
|
|
703
|
+
}
|
|
665
704
|
|
|
666
|
-
|
|
705
|
+
if "newTabId" in result:
|
|
706
|
+
response["newTabId"] = result["newTabId"]
|
|
707
|
+
|
|
708
|
+
if "timing" in result:
|
|
709
|
+
response["timing"] = result["timing"]
|
|
710
|
+
|
|
711
|
+
return response
|
|
667
712
|
except Exception as e:
|
|
668
713
|
logger.error(f"Failed to click element: {e}")
|
|
669
714
|
return {
|
|
@@ -712,10 +757,8 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
712
757
|
try:
|
|
713
758
|
ws_wrapper = await self._get_ws_wrapper()
|
|
714
759
|
|
|
715
|
-
# Handle single input mode (backward compatibility)
|
|
716
760
|
if ref is not None and text is not None:
|
|
717
761
|
result = await ws_wrapper.type(ref, text)
|
|
718
|
-
# Handle multiple inputs mode
|
|
719
762
|
elif inputs is not None:
|
|
720
763
|
result = await ws_wrapper.type_multiple(inputs)
|
|
721
764
|
else:
|
|
@@ -724,7 +767,6 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
724
767
|
"or 'inputs' for multiple inputs"
|
|
725
768
|
)
|
|
726
769
|
|
|
727
|
-
# Add tab information
|
|
728
770
|
tab_info = await ws_wrapper.get_tab_info()
|
|
729
771
|
result.update(
|
|
730
772
|
{
|
|
@@ -773,7 +815,6 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
773
815
|
ws_wrapper = await self._get_ws_wrapper()
|
|
774
816
|
result = await ws_wrapper.select(ref, value)
|
|
775
817
|
|
|
776
|
-
# Add tab information
|
|
777
818
|
tab_info = await ws_wrapper.get_tab_info()
|
|
778
819
|
result.update(
|
|
779
820
|
{
|
|
@@ -822,7 +863,6 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
822
863
|
ws_wrapper = await self._get_ws_wrapper()
|
|
823
864
|
result = await ws_wrapper.scroll(direction, amount)
|
|
824
865
|
|
|
825
|
-
# Add tab information
|
|
826
866
|
tab_info = await ws_wrapper.get_tab_info()
|
|
827
867
|
result.update(
|
|
828
868
|
{
|
|
@@ -870,7 +910,6 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
870
910
|
ws_wrapper = await self._get_ws_wrapper()
|
|
871
911
|
result = await ws_wrapper.enter()
|
|
872
912
|
|
|
873
|
-
# Add tab information
|
|
874
913
|
tab_info = await ws_wrapper.get_tab_info()
|
|
875
914
|
result.update(
|
|
876
915
|
{
|
|
@@ -922,7 +961,6 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
922
961
|
ws_wrapper = await self._get_ws_wrapper()
|
|
923
962
|
result = await ws_wrapper.mouse_control(control, x, y)
|
|
924
963
|
|
|
925
|
-
# Add tab information
|
|
926
964
|
tab_info = await ws_wrapper.get_tab_info()
|
|
927
965
|
result.update(
|
|
928
966
|
{
|
|
@@ -971,7 +1009,6 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
971
1009
|
ws_wrapper = await self._get_ws_wrapper()
|
|
972
1010
|
result = await ws_wrapper.mouse_drag(from_ref, to_ref)
|
|
973
1011
|
|
|
974
|
-
# Add tab information
|
|
975
1012
|
tab_info = await ws_wrapper.get_tab_info()
|
|
976
1013
|
result.update(
|
|
977
1014
|
{
|
|
@@ -1020,7 +1057,6 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
1020
1057
|
ws_wrapper = await self._get_ws_wrapper()
|
|
1021
1058
|
result = await ws_wrapper.press_key(keys)
|
|
1022
1059
|
|
|
1023
|
-
# Add tab information
|
|
1024
1060
|
tab_info = await ws_wrapper.get_tab_info()
|
|
1025
1061
|
result.update(
|
|
1026
1062
|
{
|
|
@@ -1069,7 +1105,6 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
1069
1105
|
ws_wrapper = await self._get_ws_wrapper()
|
|
1070
1106
|
result = await ws_wrapper.switch_tab(tab_id)
|
|
1071
1107
|
|
|
1072
|
-
# Add tab information
|
|
1073
1108
|
tab_info = await ws_wrapper.get_tab_info()
|
|
1074
1109
|
result.update(
|
|
1075
1110
|
{
|
|
@@ -1119,7 +1154,6 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
1119
1154
|
ws_wrapper = await self._get_ws_wrapper()
|
|
1120
1155
|
result = await ws_wrapper.close_tab(tab_id)
|
|
1121
1156
|
|
|
1122
|
-
# Add tab information
|
|
1123
1157
|
tab_info = await ws_wrapper.get_tab_info()
|
|
1124
1158
|
result.update(
|
|
1125
1159
|
{
|
|
@@ -1377,6 +1411,8 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
1377
1411
|
screenshot_timeout=self._screenshot_timeout,
|
|
1378
1412
|
page_stability_timeout=self._page_stability_timeout,
|
|
1379
1413
|
dom_content_loaded_timeout=self._dom_content_loaded_timeout,
|
|
1414
|
+
viewport_limit=self._viewport_limit,
|
|
1415
|
+
full_visual_mode=self._full_visual_mode,
|
|
1380
1416
|
)
|
|
1381
1417
|
|
|
1382
1418
|
def get_tools(self) -> List[FunctionTool]:
|
|
@@ -1396,7 +1432,7 @@ class HybridBrowserToolkit(BaseToolkit, RegisteredAgentToolkit):
|
|
|
1396
1432
|
"browser_select": self.browser_select,
|
|
1397
1433
|
"browser_scroll": self.browser_scroll,
|
|
1398
1434
|
"browser_enter": self.browser_enter,
|
|
1399
|
-
"
|
|
1435
|
+
"browser_mouse_control": self.browser_mouse_control,
|
|
1400
1436
|
"browser_mouse_drag": self.browser_mouse_drag,
|
|
1401
1437
|
"browser_press_key": self.browser_press_key,
|
|
1402
1438
|
"browser_wait_user": self.browser_wait_user,
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
2
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
# you may not use this file except in compliance with the License.
|
|
4
|
+
# You may obtain a copy of the License at
|
|
5
|
+
#
|
|
6
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
#
|
|
8
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
# See the License for the specific language governing permissions and
|
|
12
|
+
# limitations under the License.
|
|
13
|
+
# ========= Copyright 2023-2024 @ CAMEL-AI.org. All Rights Reserved. =========
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import platform
|
|
17
|
+
import shutil
|
|
18
|
+
import subprocess
|
|
19
|
+
from typing import Optional, Tuple
|
|
20
|
+
|
|
21
|
+
from camel.logger import get_logger
|
|
22
|
+
|
|
23
|
+
logger = get_logger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def find_command(
|
|
27
|
+
cmd_base: str,
|
|
28
|
+
windows_variants: Optional[list] = None,
|
|
29
|
+
unix_variant: Optional[str] = None,
|
|
30
|
+
) -> Optional[str]:
|
|
31
|
+
"""Find command across platforms."""
|
|
32
|
+
is_windows = platform.system() == 'Windows'
|
|
33
|
+
|
|
34
|
+
if is_windows and windows_variants:
|
|
35
|
+
for variant in windows_variants:
|
|
36
|
+
if shutil.which(variant):
|
|
37
|
+
return variant
|
|
38
|
+
else:
|
|
39
|
+
variant = unix_variant or cmd_base
|
|
40
|
+
if shutil.which(variant):
|
|
41
|
+
return variant
|
|
42
|
+
|
|
43
|
+
if shutil.which(cmd_base):
|
|
44
|
+
return cmd_base
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def create_command_not_found_error(
|
|
49
|
+
command: str, error: Optional[Exception] = None
|
|
50
|
+
) -> RuntimeError:
|
|
51
|
+
base_msg = f"{command} is not installed or not in PATH. "
|
|
52
|
+
if command in ['npm', 'node']:
|
|
53
|
+
base_msg += (
|
|
54
|
+
f"Please install Node.js{' and npm' if command == 'npm' else ''} "
|
|
55
|
+
f"from https://nodejs.org/ to use the hybrid browser toolkit."
|
|
56
|
+
)
|
|
57
|
+
if not error:
|
|
58
|
+
base_msg += (
|
|
59
|
+
" If already installed, ensure the Node.js installation "
|
|
60
|
+
"directory (e.g., C:\\Program Files\\nodejs on Windows) is "
|
|
61
|
+
"in your system PATH."
|
|
62
|
+
)
|
|
63
|
+
if error:
|
|
64
|
+
base_msg += f" Error details: {error!s}"
|
|
65
|
+
return RuntimeError(base_msg)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def create_npm_command_error(command: str, error: Exception) -> RuntimeError:
|
|
69
|
+
return RuntimeError(
|
|
70
|
+
f"Failed to run {command}: {error!s}. "
|
|
71
|
+
f"Please ensure npm is properly installed and in PATH."
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
async def check_and_install_dependencies(ts_dir: str) -> Tuple[str, str]:
|
|
76
|
+
"""Check Node.js/npm and install dependencies if needed.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Tuple of (npm_cmd, node_cmd) that can be used for running commands
|
|
80
|
+
"""
|
|
81
|
+
is_windows = platform.system() == 'Windows'
|
|
82
|
+
use_shell = is_windows
|
|
83
|
+
|
|
84
|
+
npm_cmd = find_command('npm', windows_variants=['npm.cmd', 'npm.exe'])
|
|
85
|
+
if not npm_cmd:
|
|
86
|
+
raise create_command_not_found_error('npm')
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
npm_check = subprocess.run(
|
|
90
|
+
[npm_cmd, '--version'],
|
|
91
|
+
capture_output=True,
|
|
92
|
+
text=True,
|
|
93
|
+
shell=use_shell,
|
|
94
|
+
)
|
|
95
|
+
if npm_check.returncode != 0:
|
|
96
|
+
raise RuntimeError(
|
|
97
|
+
f"npm command failed with error: {npm_check.stderr}. "
|
|
98
|
+
"Please ensure Node.js and npm are properly installed."
|
|
99
|
+
)
|
|
100
|
+
except (FileNotFoundError, OSError) as e:
|
|
101
|
+
raise create_command_not_found_error('npm', e)
|
|
102
|
+
|
|
103
|
+
node_cmd = find_command('node', windows_variants=['node.exe'])
|
|
104
|
+
if not node_cmd:
|
|
105
|
+
raise create_command_not_found_error('node')
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
node_check = subprocess.run(
|
|
109
|
+
[node_cmd, '--version'],
|
|
110
|
+
capture_output=True,
|
|
111
|
+
text=True,
|
|
112
|
+
shell=use_shell,
|
|
113
|
+
)
|
|
114
|
+
if node_check.returncode != 0:
|
|
115
|
+
raise RuntimeError(
|
|
116
|
+
f"node command failed with error: {node_check.stderr}. "
|
|
117
|
+
"Please ensure Node.js is properly installed."
|
|
118
|
+
)
|
|
119
|
+
except (FileNotFoundError, OSError) as e:
|
|
120
|
+
raise create_command_not_found_error('node', e)
|
|
121
|
+
|
|
122
|
+
node_modules_path = os.path.join(ts_dir, 'node_modules')
|
|
123
|
+
if not os.path.exists(node_modules_path):
|
|
124
|
+
logger.warning("Node modules not found. Running npm install...")
|
|
125
|
+
try:
|
|
126
|
+
install_result = subprocess.run(
|
|
127
|
+
[npm_cmd, 'install'],
|
|
128
|
+
cwd=ts_dir,
|
|
129
|
+
capture_output=True,
|
|
130
|
+
text=True,
|
|
131
|
+
shell=use_shell,
|
|
132
|
+
)
|
|
133
|
+
if install_result.returncode != 0:
|
|
134
|
+
logger.error(f"npm install failed: {install_result.stderr}")
|
|
135
|
+
raise RuntimeError(
|
|
136
|
+
f"Failed to install npm dependencies: {install_result.stderr}\n" # noqa:E501
|
|
137
|
+
f"Please run 'npm install' in {ts_dir} manually."
|
|
138
|
+
)
|
|
139
|
+
except (FileNotFoundError, OSError) as e:
|
|
140
|
+
raise create_npm_command_error("npm install", e)
|
|
141
|
+
logger.info("npm dependencies installed successfully")
|
|
142
|
+
|
|
143
|
+
dist_dir = os.path.join(ts_dir, 'dist')
|
|
144
|
+
if not os.path.exists(dist_dir) or not os.listdir(dist_dir):
|
|
145
|
+
logger.info("Building TypeScript...")
|
|
146
|
+
try:
|
|
147
|
+
build_result = subprocess.run(
|
|
148
|
+
[npm_cmd, 'run', 'build'],
|
|
149
|
+
cwd=ts_dir,
|
|
150
|
+
capture_output=True,
|
|
151
|
+
text=True,
|
|
152
|
+
shell=use_shell,
|
|
153
|
+
)
|
|
154
|
+
if build_result.returncode != 0:
|
|
155
|
+
logger.error(f"TypeScript build failed: {build_result.stderr}")
|
|
156
|
+
raise RuntimeError(
|
|
157
|
+
f"TypeScript build failed: {build_result.stderr}"
|
|
158
|
+
)
|
|
159
|
+
logger.info("TypeScript build completed successfully")
|
|
160
|
+
except (FileNotFoundError, OSError) as e:
|
|
161
|
+
raise create_npm_command_error("npm run build", e)
|
|
162
|
+
else:
|
|
163
|
+
logger.info("TypeScript already built, skipping build")
|
|
164
|
+
|
|
165
|
+
playwright_marker = os.path.join(ts_dir, '.playwright_installed')
|
|
166
|
+
if not os.path.exists(playwright_marker):
|
|
167
|
+
logger.info("Installing Playwright browsers...")
|
|
168
|
+
npx_cmd = find_command('npx', windows_variants=['npx.cmd', 'npx.exe'])
|
|
169
|
+
if not npx_cmd:
|
|
170
|
+
logger.warning(
|
|
171
|
+
"npx not found. Skipping Playwright browser installation. "
|
|
172
|
+
"Users may need to run 'npx playwright install' manually."
|
|
173
|
+
)
|
|
174
|
+
else:
|
|
175
|
+
try:
|
|
176
|
+
playwright_install = subprocess.run(
|
|
177
|
+
[npx_cmd, 'playwright', 'install'],
|
|
178
|
+
cwd=ts_dir,
|
|
179
|
+
capture_output=True,
|
|
180
|
+
text=True,
|
|
181
|
+
shell=use_shell,
|
|
182
|
+
)
|
|
183
|
+
if playwright_install.returncode == 0:
|
|
184
|
+
logger.info("Playwright browsers installed successfully")
|
|
185
|
+
with open(playwright_marker, 'w') as f:
|
|
186
|
+
f.write('installed')
|
|
187
|
+
else:
|
|
188
|
+
logger.warning(
|
|
189
|
+
f"Playwright browser installation failed: "
|
|
190
|
+
f"{playwright_install.stderr}"
|
|
191
|
+
)
|
|
192
|
+
logger.warning(
|
|
193
|
+
"Users may need to run 'npx playwright install' "
|
|
194
|
+
"manually"
|
|
195
|
+
)
|
|
196
|
+
except (FileNotFoundError, OSError) as e:
|
|
197
|
+
logger.warning(
|
|
198
|
+
f"Failed to install Playwright browsers: {e!s}. "
|
|
199
|
+
f"Users may need to run 'npx playwright install' "
|
|
200
|
+
f"manually"
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
return npm_cmd, node_cmd
|