camel-ai 0.2.71a7__py3-none-any.whl → 0.2.71a9__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/societies/workforce/single_agent_worker.py +53 -9
- camel/societies/workforce/task_channel.py +4 -1
- camel/societies/workforce/workforce.py +146 -14
- camel/tasks/task.py +104 -4
- camel/toolkits/file_write_toolkit.py +19 -8
- camel/toolkits/hybrid_browser_toolkit/actions.py +28 -18
- camel/toolkits/hybrid_browser_toolkit/agent.py +7 -1
- camel/toolkits/hybrid_browser_toolkit/browser_session.py +48 -18
- camel/toolkits/hybrid_browser_toolkit/config_loader.py +447 -0
- camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit.py +272 -85
- camel/toolkits/hybrid_browser_toolkit/snapshot.py +5 -4
- camel/toolkits/hybrid_browser_toolkit/unified_analyzer.js +572 -17
- camel/toolkits/note_taking_toolkit.py +24 -9
- camel/toolkits/pptx_toolkit.py +21 -8
- camel/toolkits/search_toolkit.py +15 -5
- {camel_ai-0.2.71a7.dist-info → camel_ai-0.2.71a9.dist-info}/METADATA +1 -1
- {camel_ai-0.2.71a7.dist-info → camel_ai-0.2.71a9.dist-info}/RECORD +20 -20
- camel/toolkits/hybrid_browser_toolkit/stealth_config.py +0 -116
- {camel_ai-0.2.71a7.dist-info → camel_ai-0.2.71a9.dist-info}/WHEEL +0 -0
- {camel_ai-0.2.71a7.dist-info → camel_ai-0.2.71a9.dist-info}/licenses/LICENSE +0 -0
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
import asyncio
|
|
15
15
|
from typing import TYPE_CHECKING, Any, Dict, Optional
|
|
16
16
|
|
|
17
|
+
from .config_loader import ConfigLoader
|
|
18
|
+
|
|
17
19
|
if TYPE_CHECKING:
|
|
18
20
|
from playwright.async_api import Page
|
|
19
21
|
|
|
@@ -21,15 +23,24 @@ if TYPE_CHECKING:
|
|
|
21
23
|
class ActionExecutor:
|
|
22
24
|
r"""Executes high-level actions (click, type …) on a Playwright Page."""
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
page: "Page",
|
|
29
|
+
session: Optional[Any] = None,
|
|
30
|
+
default_timeout: Optional[int] = None,
|
|
31
|
+
short_timeout: Optional[int] = None,
|
|
32
|
+
max_scroll_amount: Optional[int] = None,
|
|
33
|
+
):
|
|
30
34
|
self.page = page
|
|
31
35
|
self.session = session # HybridBrowserSession instance
|
|
32
36
|
|
|
37
|
+
# Configure timeouts using the config file with optional overrides
|
|
38
|
+
self.default_timeout = ConfigLoader.get_action_timeout(default_timeout)
|
|
39
|
+
self.short_timeout = ConfigLoader.get_short_timeout(short_timeout)
|
|
40
|
+
self.max_scroll_amount = ConfigLoader.get_max_scroll_amount(
|
|
41
|
+
max_scroll_amount
|
|
42
|
+
)
|
|
43
|
+
|
|
33
44
|
# ------------------------------------------------------------------
|
|
34
45
|
# Public helpers
|
|
35
46
|
# ------------------------------------------------------------------
|
|
@@ -139,7 +150,7 @@ class ActionExecutor:
|
|
|
139
150
|
try:
|
|
140
151
|
if self.session:
|
|
141
152
|
async with self.page.context.expect_page(
|
|
142
|
-
timeout=self.
|
|
153
|
+
timeout=self.short_timeout
|
|
143
154
|
) as new_page_info:
|
|
144
155
|
await element.click(modifiers=["ControlOrMeta"])
|
|
145
156
|
new_page = await new_page_info.value
|
|
@@ -188,7 +199,7 @@ class ActionExecutor:
|
|
|
188
199
|
|
|
189
200
|
# Fallback to normal force click if ctrl+click fails
|
|
190
201
|
try:
|
|
191
|
-
await element.click(force=True, timeout=self.
|
|
202
|
+
await element.click(force=True, timeout=self.default_timeout)
|
|
192
203
|
details["click_method"] = "playwright_force_click"
|
|
193
204
|
return {
|
|
194
205
|
"message": f"Fallback clicked element: {found_selector}",
|
|
@@ -224,7 +235,7 @@ class ActionExecutor:
|
|
|
224
235
|
}
|
|
225
236
|
|
|
226
237
|
try:
|
|
227
|
-
await self.page.fill(target, text, timeout=self.
|
|
238
|
+
await self.page.fill(target, text, timeout=self.short_timeout)
|
|
228
239
|
return {
|
|
229
240
|
"message": f"Typed '{text}' into {target}",
|
|
230
241
|
"details": details,
|
|
@@ -254,7 +265,7 @@ class ActionExecutor:
|
|
|
254
265
|
|
|
255
266
|
try:
|
|
256
267
|
await self.page.select_option(
|
|
257
|
-
target, value, timeout=self.
|
|
268
|
+
target, value, timeout=self.default_timeout
|
|
258
269
|
)
|
|
259
270
|
return {
|
|
260
271
|
"message": f"Selected '{value}' in {target}",
|
|
@@ -283,7 +294,7 @@ class ActionExecutor:
|
|
|
283
294
|
details["wait_type"] = "selector"
|
|
284
295
|
details["selector"] = sel
|
|
285
296
|
await self.page.wait_for_selector(
|
|
286
|
-
sel, timeout=self.
|
|
297
|
+
sel, timeout=self.default_timeout
|
|
287
298
|
)
|
|
288
299
|
return {"message": f"Waited for {sel}", "details": details}
|
|
289
300
|
return {
|
|
@@ -303,7 +314,7 @@ class ActionExecutor:
|
|
|
303
314
|
target = f"[aria-ref='{ref}']"
|
|
304
315
|
details = {"ref": ref, "target": target}
|
|
305
316
|
|
|
306
|
-
await self.page.wait_for_selector(target, timeout=self.
|
|
317
|
+
await self.page.wait_for_selector(target, timeout=self.default_timeout)
|
|
307
318
|
txt = await self.page.text_content(target)
|
|
308
319
|
|
|
309
320
|
details["extracted_text"] = txt
|
|
@@ -337,9 +348,9 @@ class ActionExecutor:
|
|
|
337
348
|
# Safely convert amount to integer and clamp to reasonable range
|
|
338
349
|
amount_int = int(amount)
|
|
339
350
|
amount_int = max(
|
|
340
|
-
-self.
|
|
341
|
-
min(self.
|
|
342
|
-
) # Clamp to
|
|
351
|
+
-self.max_scroll_amount,
|
|
352
|
+
min(self.max_scroll_amount, amount_int),
|
|
353
|
+
) # Clamp to max_scroll_amount range
|
|
343
354
|
details["actual_amount"] = amount_int
|
|
344
355
|
except (ValueError, TypeError):
|
|
345
356
|
return {
|
|
@@ -366,7 +377,6 @@ class ActionExecutor:
|
|
|
366
377
|
|
|
367
378
|
# Press Enter on whatever element currently has focus
|
|
368
379
|
await self.page.keyboard.press("Enter")
|
|
369
|
-
await asyncio.sleep(0.3)
|
|
370
380
|
return {
|
|
371
381
|
"message": "Pressed Enter on focused element",
|
|
372
382
|
"details": details,
|
|
@@ -378,13 +388,13 @@ class ActionExecutor:
|
|
|
378
388
|
try:
|
|
379
389
|
# Wait for basic DOM content loading
|
|
380
390
|
await self.page.wait_for_load_state(
|
|
381
|
-
'domcontentloaded', timeout=self.
|
|
391
|
+
'domcontentloaded', timeout=self.short_timeout
|
|
382
392
|
)
|
|
383
393
|
|
|
384
394
|
# Try to wait for network idle briefly
|
|
385
395
|
try:
|
|
386
396
|
await self.page.wait_for_load_state(
|
|
387
|
-
'networkidle', timeout=self.
|
|
397
|
+
'networkidle', timeout=self.short_timeout
|
|
388
398
|
)
|
|
389
399
|
except Exception:
|
|
390
400
|
pass # Network idle is optional
|
|
@@ -81,9 +81,15 @@ what was accomplished
|
|
|
81
81
|
headless: bool = False,
|
|
82
82
|
stealth: bool = False,
|
|
83
83
|
model_backend: Optional[BaseModelBackend] = None,
|
|
84
|
+
default_timeout: Optional[int] = None,
|
|
85
|
+
short_timeout: Optional[int] = None,
|
|
84
86
|
):
|
|
85
87
|
self._session = HybridBrowserSession(
|
|
86
|
-
headless=headless,
|
|
88
|
+
headless=headless,
|
|
89
|
+
user_data_dir=user_data_dir,
|
|
90
|
+
stealth=stealth,
|
|
91
|
+
default_timeout=default_timeout,
|
|
92
|
+
short_timeout=short_timeout,
|
|
87
93
|
)
|
|
88
94
|
from camel.agents import ChatAgent
|
|
89
95
|
|
|
@@ -19,8 +19,8 @@ from typing import TYPE_CHECKING, Any, ClassVar, Dict, List, Optional, Tuple
|
|
|
19
19
|
from camel.logger import get_logger
|
|
20
20
|
|
|
21
21
|
from .actions import ActionExecutor
|
|
22
|
+
from .config_loader import ConfigLoader
|
|
22
23
|
from .snapshot import PageSnapshot
|
|
23
|
-
from .stealth_config import StealthConfig
|
|
24
24
|
|
|
25
25
|
if TYPE_CHECKING:
|
|
26
26
|
from playwright.async_api import (
|
|
@@ -44,10 +44,6 @@ class HybridBrowserSession:
|
|
|
44
44
|
This class is a singleton per event-loop and session-id combination.
|
|
45
45
|
"""
|
|
46
46
|
|
|
47
|
-
# Configuration constants
|
|
48
|
-
DEFAULT_NAVIGATION_TIMEOUT = 10000 # 10 seconds
|
|
49
|
-
NETWORK_IDLE_TIMEOUT = 5000 # 5 seconds
|
|
50
|
-
|
|
51
47
|
# Class-level registry for singleton instances
|
|
52
48
|
# Format: {(loop_id, session_id): HybridBrowserSession}
|
|
53
49
|
_instances: ClassVar[Dict[Tuple[Any, str], "HybridBrowserSession"]] = {}
|
|
@@ -63,6 +59,10 @@ class HybridBrowserSession:
|
|
|
63
59
|
user_data_dir: Optional[str] = None,
|
|
64
60
|
stealth: bool = False,
|
|
65
61
|
session_id: Optional[str] = None,
|
|
62
|
+
default_timeout: Optional[int] = None,
|
|
63
|
+
short_timeout: Optional[int] = None,
|
|
64
|
+
navigation_timeout: Optional[int] = None,
|
|
65
|
+
network_idle_timeout: Optional[int] = None,
|
|
66
66
|
) -> "HybridBrowserSession":
|
|
67
67
|
# Create a unique key for this event loop and session combination
|
|
68
68
|
# We defer the event loop lookup to avoid issues with creation
|
|
@@ -75,6 +75,10 @@ class HybridBrowserSession:
|
|
|
75
75
|
"user_data_dir": user_data_dir,
|
|
76
76
|
"stealth": stealth,
|
|
77
77
|
"session_id": session_id,
|
|
78
|
+
"default_timeout": default_timeout,
|
|
79
|
+
"short_timeout": short_timeout,
|
|
80
|
+
"navigation_timeout": navigation_timeout,
|
|
81
|
+
"network_idle_timeout": network_idle_timeout,
|
|
78
82
|
}
|
|
79
83
|
return instance
|
|
80
84
|
|
|
@@ -126,6 +130,10 @@ class HybridBrowserSession:
|
|
|
126
130
|
user_data_dir: Optional[str] = None,
|
|
127
131
|
stealth: bool = False,
|
|
128
132
|
session_id: Optional[str] = None,
|
|
133
|
+
default_timeout: Optional[int] = None,
|
|
134
|
+
short_timeout: Optional[int] = None,
|
|
135
|
+
navigation_timeout: Optional[int] = None,
|
|
136
|
+
network_idle_timeout: Optional[int] = None,
|
|
129
137
|
):
|
|
130
138
|
if self._initialized:
|
|
131
139
|
return
|
|
@@ -136,12 +144,27 @@ class HybridBrowserSession:
|
|
|
136
144
|
self._stealth = stealth
|
|
137
145
|
self._session_id = session_id or "default"
|
|
138
146
|
|
|
147
|
+
# Store timeout configuration for ActionExecutor instances and
|
|
148
|
+
# browser operations
|
|
149
|
+
self._default_timeout = default_timeout
|
|
150
|
+
self._short_timeout = short_timeout
|
|
151
|
+
self._navigation_timeout = ConfigLoader.get_navigation_timeout(
|
|
152
|
+
navigation_timeout
|
|
153
|
+
)
|
|
154
|
+
self._network_idle_timeout = ConfigLoader.get_network_idle_timeout(
|
|
155
|
+
network_idle_timeout
|
|
156
|
+
)
|
|
157
|
+
|
|
139
158
|
# Initialize _creation_params to fix linter error
|
|
140
159
|
self._creation_params = {
|
|
141
160
|
"headless": headless,
|
|
142
161
|
"user_data_dir": user_data_dir,
|
|
143
162
|
"stealth": stealth,
|
|
144
163
|
"session_id": session_id,
|
|
164
|
+
"default_timeout": default_timeout,
|
|
165
|
+
"short_timeout": short_timeout,
|
|
166
|
+
"navigation_timeout": navigation_timeout,
|
|
167
|
+
"network_idle_timeout": network_idle_timeout,
|
|
145
168
|
}
|
|
146
169
|
|
|
147
170
|
self._playwright: Optional[Playwright] = None
|
|
@@ -164,7 +187,8 @@ class HybridBrowserSession:
|
|
|
164
187
|
self._stealth_config: Optional[Dict[str, Any]] = None
|
|
165
188
|
if self._stealth:
|
|
166
189
|
self._stealth_script = self._load_stealth_script()
|
|
167
|
-
|
|
190
|
+
stealth_config_class = ConfigLoader.get_stealth_config()
|
|
191
|
+
self._stealth_config = stealth_config_class.get_stealth_config()
|
|
168
192
|
|
|
169
193
|
def _load_stealth_script(self) -> str:
|
|
170
194
|
r"""Load the stealth JavaScript script from file."""
|
|
@@ -231,9 +255,7 @@ class HybridBrowserSession:
|
|
|
231
255
|
# Navigate if URL provided
|
|
232
256
|
if url:
|
|
233
257
|
try:
|
|
234
|
-
await new_page.goto(
|
|
235
|
-
url, timeout=self.DEFAULT_NAVIGATION_TIMEOUT
|
|
236
|
-
)
|
|
258
|
+
await new_page.goto(url, timeout=self._navigation_timeout)
|
|
237
259
|
await new_page.wait_for_load_state('domcontentloaded')
|
|
238
260
|
except Exception as e:
|
|
239
261
|
logger.warning(f"Failed to navigate new tab to {url}: {e}")
|
|
@@ -319,7 +341,12 @@ class HybridBrowserSession:
|
|
|
319
341
|
await self._page.bring_to_front()
|
|
320
342
|
|
|
321
343
|
# Update executor and snapshot for new tab
|
|
322
|
-
self.executor = ActionExecutor(
|
|
344
|
+
self.executor = ActionExecutor(
|
|
345
|
+
self._page,
|
|
346
|
+
self,
|
|
347
|
+
default_timeout=self._default_timeout,
|
|
348
|
+
short_timeout=self._short_timeout,
|
|
349
|
+
)
|
|
323
350
|
self.snapshot = PageSnapshot(self._page)
|
|
324
351
|
|
|
325
352
|
logger.info(f"Switched to tab {tab_index}")
|
|
@@ -506,14 +533,17 @@ class HybridBrowserSession:
|
|
|
506
533
|
logger.warning(f"Failed to apply stealth script: {e}")
|
|
507
534
|
|
|
508
535
|
# Set up timeout for navigation
|
|
509
|
-
self._page.set_default_navigation_timeout(
|
|
510
|
-
|
|
511
|
-
)
|
|
512
|
-
self._page.set_default_timeout(self.DEFAULT_NAVIGATION_TIMEOUT)
|
|
536
|
+
self._page.set_default_navigation_timeout(self._navigation_timeout)
|
|
537
|
+
self._page.set_default_timeout(self._navigation_timeout)
|
|
513
538
|
|
|
514
539
|
# Initialize utilities
|
|
515
540
|
self.snapshot = PageSnapshot(self._page)
|
|
516
|
-
self.executor = ActionExecutor(
|
|
541
|
+
self.executor = ActionExecutor(
|
|
542
|
+
self._page,
|
|
543
|
+
self,
|
|
544
|
+
default_timeout=self._default_timeout,
|
|
545
|
+
short_timeout=self._short_timeout,
|
|
546
|
+
)
|
|
517
547
|
self._current_tab_index = 0
|
|
518
548
|
|
|
519
549
|
logger.info("Browser session initialized successfully")
|
|
@@ -579,7 +609,7 @@ class HybridBrowserSession:
|
|
|
579
609
|
await page.close()
|
|
580
610
|
logger.debug(
|
|
581
611
|
f"Closed page: "
|
|
582
|
-
f"{page.url if hasattr(page, 'url') else 'unknown'}" # noqa:
|
|
612
|
+
f"{page.url if hasattr(page, 'url') else 'unknown'}" # noqa:E501
|
|
583
613
|
)
|
|
584
614
|
except Exception as e:
|
|
585
615
|
logger.warning(f"Error closing page: {e}")
|
|
@@ -668,13 +698,13 @@ class HybridBrowserSession:
|
|
|
668
698
|
await self.ensure_browser()
|
|
669
699
|
page = await self.get_page()
|
|
670
700
|
|
|
671
|
-
await page.goto(url, timeout=self.
|
|
701
|
+
await page.goto(url, timeout=self._navigation_timeout)
|
|
672
702
|
await page.wait_for_load_state('domcontentloaded')
|
|
673
703
|
|
|
674
704
|
# Try to wait for network idle
|
|
675
705
|
try:
|
|
676
706
|
await page.wait_for_load_state(
|
|
677
|
-
'networkidle', timeout=self.
|
|
707
|
+
'networkidle', timeout=self._network_idle_timeout
|
|
678
708
|
)
|
|
679
709
|
except Exception:
|
|
680
710
|
logger.debug("Network idle timeout - continuing anyway")
|