camel-ai 0.2.73a4__py3-none-any.whl → 0.2.80a2__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.
- camel/__init__.py +1 -1
- camel/agents/_utils.py +38 -0
- camel/agents/chat_agent.py +2217 -519
- camel/agents/mcp_agent.py +30 -27
- camel/configs/__init__.py +15 -0
- camel/configs/aihubmix_config.py +88 -0
- camel/configs/amd_config.py +70 -0
- camel/configs/cometapi_config.py +104 -0
- camel/configs/minimax_config.py +93 -0
- camel/configs/nebius_config.py +103 -0
- camel/data_collectors/alpaca_collector.py +15 -6
- camel/datasets/base_generator.py +39 -10
- camel/environments/single_step.py +28 -3
- camel/environments/tic_tac_toe.py +1 -1
- camel/interpreters/__init__.py +2 -0
- camel/interpreters/docker/Dockerfile +3 -12
- camel/interpreters/e2b_interpreter.py +34 -1
- camel/interpreters/microsandbox_interpreter.py +395 -0
- camel/loaders/__init__.py +11 -2
- camel/loaders/chunkr_reader.py +9 -0
- camel/memories/agent_memories.py +48 -4
- camel/memories/base.py +26 -0
- camel/memories/blocks/chat_history_block.py +122 -4
- camel/memories/context_creators/score_based.py +25 -384
- camel/memories/records.py +88 -8
- camel/messages/base.py +153 -34
- camel/models/__init__.py +10 -0
- camel/models/aihubmix_model.py +83 -0
- camel/models/aiml_model.py +1 -16
- camel/models/amd_model.py +101 -0
- camel/models/anthropic_model.py +6 -19
- camel/models/aws_bedrock_model.py +2 -33
- camel/models/azure_openai_model.py +114 -89
- camel/models/base_audio_model.py +3 -1
- camel/models/base_model.py +32 -14
- camel/models/cohere_model.py +1 -16
- camel/models/cometapi_model.py +83 -0
- camel/models/crynux_model.py +1 -16
- camel/models/deepseek_model.py +1 -16
- camel/models/fish_audio_model.py +6 -0
- camel/models/gemini_model.py +36 -18
- camel/models/groq_model.py +1 -17
- camel/models/internlm_model.py +1 -16
- camel/models/litellm_model.py +1 -16
- camel/models/lmstudio_model.py +1 -17
- camel/models/minimax_model.py +83 -0
- camel/models/mistral_model.py +1 -16
- camel/models/model_factory.py +27 -1
- camel/models/modelscope_model.py +1 -16
- camel/models/moonshot_model.py +105 -24
- camel/models/nebius_model.py +83 -0
- camel/models/nemotron_model.py +0 -5
- camel/models/netmind_model.py +1 -16
- camel/models/novita_model.py +1 -16
- camel/models/nvidia_model.py +1 -16
- camel/models/ollama_model.py +4 -19
- camel/models/openai_compatible_model.py +62 -41
- camel/models/openai_model.py +62 -57
- camel/models/openrouter_model.py +1 -17
- camel/models/ppio_model.py +1 -16
- camel/models/qianfan_model.py +1 -16
- camel/models/qwen_model.py +1 -16
- camel/models/reka_model.py +1 -16
- camel/models/samba_model.py +34 -47
- camel/models/sglang_model.py +64 -31
- camel/models/siliconflow_model.py +1 -16
- camel/models/stub_model.py +0 -4
- camel/models/togetherai_model.py +1 -16
- camel/models/vllm_model.py +1 -16
- camel/models/volcano_model.py +0 -17
- camel/models/watsonx_model.py +1 -16
- camel/models/yi_model.py +1 -16
- camel/models/zhipuai_model.py +60 -16
- 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/__init__.py +2 -0
- camel/societies/workforce/__init__.py +2 -0
- camel/societies/workforce/events.py +122 -0
- camel/societies/workforce/prompts.py +146 -66
- camel/societies/workforce/role_playing_worker.py +15 -11
- camel/societies/workforce/single_agent_worker.py +302 -65
- camel/societies/workforce/structured_output_handler.py +30 -18
- camel/societies/workforce/task_channel.py +163 -27
- camel/societies/workforce/utils.py +107 -13
- camel/societies/workforce/workflow_memory_manager.py +772 -0
- camel/societies/workforce/workforce.py +1949 -579
- camel/societies/workforce/workforce_callback.py +74 -0
- camel/societies/workforce/workforce_logger.py +168 -145
- camel/societies/workforce/workforce_metrics.py +33 -0
- camel/storages/key_value_storages/json.py +15 -2
- camel/storages/key_value_storages/mem0_cloud.py +48 -47
- camel/storages/object_storages/google_cloud.py +1 -1
- camel/storages/vectordb_storages/oceanbase.py +13 -13
- camel/storages/vectordb_storages/qdrant.py +3 -3
- camel/storages/vectordb_storages/tidb.py +8 -6
- camel/tasks/task.py +4 -3
- camel/toolkits/__init__.py +20 -7
- camel/toolkits/aci_toolkit.py +45 -0
- camel/toolkits/base.py +6 -4
- camel/toolkits/code_execution.py +28 -1
- camel/toolkits/context_summarizer_toolkit.py +684 -0
- camel/toolkits/dappier_toolkit.py +5 -1
- camel/toolkits/dingtalk.py +1135 -0
- camel/toolkits/edgeone_pages_mcp_toolkit.py +11 -31
- camel/toolkits/excel_toolkit.py +1 -1
- camel/toolkits/{file_write_toolkit.py → file_toolkit.py} +430 -36
- camel/toolkits/function_tool.py +13 -3
- camel/toolkits/github_toolkit.py +104 -17
- camel/toolkits/gmail_toolkit.py +1839 -0
- camel/toolkits/google_calendar_toolkit.py +38 -4
- camel/toolkits/google_drive_mcp_toolkit.py +12 -31
- camel/toolkits/hybrid_browser_toolkit/config_loader.py +15 -0
- camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit.py +77 -8
- camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit_ts.py +884 -88
- 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 +959 -89
- camel/toolkits/hybrid_browser_toolkit/ts/src/config-loader.ts +9 -2
- camel/toolkits/hybrid_browser_toolkit/ts/src/hybrid-browser-toolkit.ts +281 -213
- 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 +23 -3
- camel/toolkits/hybrid_browser_toolkit/ts/websocket-server.js +72 -7
- camel/toolkits/hybrid_browser_toolkit/ws_wrapper.py +582 -132
- camel/toolkits/hybrid_browser_toolkit_py/actions.py +158 -0
- camel/toolkits/hybrid_browser_toolkit_py/browser_session.py +55 -8
- camel/toolkits/hybrid_browser_toolkit_py/config_loader.py +43 -0
- camel/toolkits/hybrid_browser_toolkit_py/hybrid_browser_toolkit.py +321 -8
- camel/toolkits/hybrid_browser_toolkit_py/snapshot.py +10 -4
- camel/toolkits/hybrid_browser_toolkit_py/unified_analyzer.js +45 -4
- camel/toolkits/{openai_image_toolkit.py → image_generation_toolkit.py} +151 -53
- camel/toolkits/klavis_toolkit.py +5 -1
- camel/toolkits/markitdown_toolkit.py +27 -1
- camel/toolkits/math_toolkit.py +64 -10
- camel/toolkits/mcp_toolkit.py +366 -71
- camel/toolkits/memory_toolkit.py +5 -1
- camel/toolkits/message_integration.py +18 -13
- camel/toolkits/minimax_mcp_toolkit.py +195 -0
- camel/toolkits/note_taking_toolkit.py +19 -10
- camel/toolkits/notion_mcp_toolkit.py +16 -26
- camel/toolkits/openbb_toolkit.py +5 -1
- 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/search_toolkit.py +264 -91
- camel/toolkits/slack_toolkit.py +64 -10
- camel/toolkits/terminal_toolkit/__init__.py +18 -0
- camel/toolkits/terminal_toolkit/terminal_toolkit.py +957 -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/toolkits/zapier_toolkit.py +5 -1
- camel/types/__init__.py +2 -2
- camel/types/enums.py +274 -7
- camel/types/openai_types.py +2 -2
- camel/types/unified_model_type.py +15 -0
- camel/utils/commons.py +36 -5
- camel/utils/constants.py +3 -0
- camel/utils/context_utils.py +1003 -0
- camel/utils/mcp.py +138 -4
- camel/utils/token_counting.py +43 -20
- {camel_ai-0.2.73a4.dist-info → camel_ai-0.2.80a2.dist-info}/METADATA +223 -83
- {camel_ai-0.2.73a4.dist-info → camel_ai-0.2.80a2.dist-info}/RECORD +170 -141
- camel/loaders/pandas_reader.py +0 -368
- camel/toolkits/openai_agent_toolkit.py +0 -135
- camel/toolkits/terminal_toolkit.py +0 -1550
- {camel_ai-0.2.73a4.dist-info → camel_ai-0.2.80a2.dist-info}/WHEEL +0 -0
- {camel_ai-0.2.73a4.dist-info → camel_ai-0.2.80a2.dist-info}/licenses/LICENSE +0 -0
|
@@ -73,6 +73,9 @@ class ActionExecutor:
|
|
|
73
73
|
"extract": self._extract,
|
|
74
74
|
"scroll": self._scroll,
|
|
75
75
|
"enter": self._enter,
|
|
76
|
+
"mouse_control": self._mouse_control,
|
|
77
|
+
"mouse_drag": self._mouse_drag,
|
|
78
|
+
"press_key": self._press_key,
|
|
76
79
|
}.get(action_type)
|
|
77
80
|
|
|
78
81
|
if handler is None:
|
|
@@ -382,6 +385,150 @@ class ActionExecutor:
|
|
|
382
385
|
"details": details,
|
|
383
386
|
}
|
|
384
387
|
|
|
388
|
+
async def _mouse_control(self, action: Dict[str, Any]) -> Dict[str, Any]:
|
|
389
|
+
r"""Handle mouse_control action based on the coordinates"""
|
|
390
|
+
control = action.get("control", "click")
|
|
391
|
+
x_coord = action.get("x", 0)
|
|
392
|
+
y_coord = action.get("y", 0)
|
|
393
|
+
|
|
394
|
+
details = {
|
|
395
|
+
"action_type": "mouse_control",
|
|
396
|
+
"target": f"coordinates : ({x_coord}, {y_coord})",
|
|
397
|
+
}
|
|
398
|
+
try:
|
|
399
|
+
if not self._valid_coordinates(x_coord, y_coord):
|
|
400
|
+
raise ValueError(
|
|
401
|
+
"Invalid coordinates, outside viewport bounds :"
|
|
402
|
+
f"({x_coord}, {y_coord})"
|
|
403
|
+
)
|
|
404
|
+
match control:
|
|
405
|
+
case "click":
|
|
406
|
+
await self.page.mouse.click(x_coord, y_coord)
|
|
407
|
+
message = "Action 'click' performed on the target"
|
|
408
|
+
|
|
409
|
+
case "right_click":
|
|
410
|
+
await self.page.mouse.click(
|
|
411
|
+
x_coord, y_coord, button="right"
|
|
412
|
+
)
|
|
413
|
+
message = "Action 'right_click' performed on the target"
|
|
414
|
+
|
|
415
|
+
case "dblclick":
|
|
416
|
+
await self.page.mouse.dblclick(x_coord, y_coord)
|
|
417
|
+
message = "Action 'dblclick' performed on the target"
|
|
418
|
+
|
|
419
|
+
case _:
|
|
420
|
+
return {
|
|
421
|
+
"message": f"Invalid control action {control}",
|
|
422
|
+
"details": details,
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return {"message": message, "details": details}
|
|
426
|
+
except Exception as e:
|
|
427
|
+
return {"message": f"Action failed: {e}", "details": details}
|
|
428
|
+
|
|
429
|
+
async def _mouse_drag(self, action: Dict[str, Any]) -> Dict[str, Any]:
|
|
430
|
+
r"""Handle mouse_drag action using ref IDs"""
|
|
431
|
+
from_ref = action.get("from_ref")
|
|
432
|
+
to_ref = action.get("to_ref")
|
|
433
|
+
|
|
434
|
+
if not from_ref or not to_ref:
|
|
435
|
+
return {
|
|
436
|
+
"message": "Error: mouse_drag requires from_ref and to_ref",
|
|
437
|
+
"details": {"error": "missing_refs"},
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
from_selector = f"[aria-ref='{from_ref}']"
|
|
441
|
+
to_selector = f"[aria-ref='{to_ref}']"
|
|
442
|
+
|
|
443
|
+
details = {
|
|
444
|
+
"action_type": "mouse_drag",
|
|
445
|
+
"from_ref": from_ref,
|
|
446
|
+
"to_ref": to_ref,
|
|
447
|
+
"from_selector": from_selector,
|
|
448
|
+
"to_selector": to_selector,
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
try:
|
|
452
|
+
# Get the source element
|
|
453
|
+
from_element = self.page.locator(from_selector)
|
|
454
|
+
from_count = await from_element.count()
|
|
455
|
+
if from_count == 0:
|
|
456
|
+
raise ValueError(
|
|
457
|
+
f"Source element with ref '{from_ref}' not found"
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
# Get the target element
|
|
461
|
+
to_element = self.page.locator(to_selector)
|
|
462
|
+
to_count = await to_element.count()
|
|
463
|
+
if to_count == 0:
|
|
464
|
+
raise ValueError(
|
|
465
|
+
f"Target element with ref '{to_ref}' not found"
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
# Get bounding boxes
|
|
469
|
+
from_box = await from_element.first.bounding_box()
|
|
470
|
+
to_box = await to_element.first.bounding_box()
|
|
471
|
+
|
|
472
|
+
if not from_box:
|
|
473
|
+
raise ValueError(
|
|
474
|
+
f"Could not get bounding box for source element "
|
|
475
|
+
f"with ref '{from_ref}'"
|
|
476
|
+
)
|
|
477
|
+
if not to_box:
|
|
478
|
+
raise ValueError(
|
|
479
|
+
f"Could not get bounding box for target element "
|
|
480
|
+
f"with ref '{to_ref}'"
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
# Calculate center coordinates
|
|
484
|
+
from_x = from_box['x'] + from_box['width'] / 2
|
|
485
|
+
from_y = from_box['y'] + from_box['height'] / 2
|
|
486
|
+
to_x = to_box['x'] + to_box['width'] / 2
|
|
487
|
+
to_y = to_box['y'] + to_box['height'] / 2
|
|
488
|
+
|
|
489
|
+
details.update(
|
|
490
|
+
{
|
|
491
|
+
"from_coordinates": {"x": from_x, "y": from_y},
|
|
492
|
+
"to_coordinates": {"x": to_x, "y": to_y},
|
|
493
|
+
}
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
# Perform the drag operation
|
|
497
|
+
await self.page.mouse.move(from_x, from_y)
|
|
498
|
+
await self.page.mouse.down()
|
|
499
|
+
# Destination coordinates
|
|
500
|
+
await self.page.mouse.move(to_x, to_y)
|
|
501
|
+
await self.page.mouse.up()
|
|
502
|
+
|
|
503
|
+
return {
|
|
504
|
+
"message": (
|
|
505
|
+
f"Dragged from element [ref={from_ref}] to element "
|
|
506
|
+
f"[ref={to_ref}]"
|
|
507
|
+
),
|
|
508
|
+
"details": details,
|
|
509
|
+
}
|
|
510
|
+
except Exception as e:
|
|
511
|
+
return {"message": f"Action failed: {e}", "details": details}
|
|
512
|
+
|
|
513
|
+
async def _press_key(self, action: Dict[str, Any]) -> Dict[str, Any]:
|
|
514
|
+
r"""Handle press_key action by combining the keys in a list."""
|
|
515
|
+
keys = action.get("keys", [])
|
|
516
|
+
if not keys:
|
|
517
|
+
return {
|
|
518
|
+
"message": "Error: No keys specified",
|
|
519
|
+
"details": {"action_type": "press_key", "keys": ""},
|
|
520
|
+
}
|
|
521
|
+
combined_keys = "+".join(keys)
|
|
522
|
+
details = {"action_type": "press_key", "keys": combined_keys}
|
|
523
|
+
try:
|
|
524
|
+
await self.page.keyboard.press(combined_keys)
|
|
525
|
+
return {
|
|
526
|
+
"message": "Pressed keys in the browser",
|
|
527
|
+
"details": details,
|
|
528
|
+
}
|
|
529
|
+
except Exception as e:
|
|
530
|
+
return {"message": f"Action failed: {e}", "details": details}
|
|
531
|
+
|
|
385
532
|
# utilities
|
|
386
533
|
async def _wait_dom_stable(self) -> None:
|
|
387
534
|
r"""Wait for DOM to become stable before executing actions."""
|
|
@@ -402,6 +549,17 @@ class ActionExecutor:
|
|
|
402
549
|
except Exception:
|
|
403
550
|
pass # Don't fail if wait times out
|
|
404
551
|
|
|
552
|
+
def _valid_coordinates(self, x_coord: float, y_coord: float) -> bool:
|
|
553
|
+
r"""Validate given coordinates against viewport bounds."""
|
|
554
|
+
viewport = self.page.viewport_size
|
|
555
|
+
if not viewport:
|
|
556
|
+
raise ValueError("Viewport size not available from current page.")
|
|
557
|
+
|
|
558
|
+
return (
|
|
559
|
+
0 <= x_coord <= viewport['width']
|
|
560
|
+
and 0 <= y_coord <= viewport['height']
|
|
561
|
+
)
|
|
562
|
+
|
|
405
563
|
# static helpers
|
|
406
564
|
@staticmethod
|
|
407
565
|
def should_update_snapshot(action: Dict[str, Any]) -> bool:
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
from __future__ import annotations
|
|
15
15
|
|
|
16
16
|
import asyncio
|
|
17
|
+
from collections import deque
|
|
17
18
|
from typing import TYPE_CHECKING, Any, ClassVar, Dict, List, Optional, Tuple
|
|
18
19
|
|
|
19
20
|
from camel.logger import get_logger
|
|
@@ -26,6 +27,7 @@ if TYPE_CHECKING:
|
|
|
26
27
|
from playwright.async_api import (
|
|
27
28
|
Browser,
|
|
28
29
|
BrowserContext,
|
|
30
|
+
ConsoleMessage,
|
|
29
31
|
Page,
|
|
30
32
|
Playwright,
|
|
31
33
|
)
|
|
@@ -188,7 +190,9 @@ class HybridBrowserSession:
|
|
|
188
190
|
|
|
189
191
|
# Dictionary-based tab management with monotonic IDs
|
|
190
192
|
self._pages: Dict[str, Page] = {} # tab_id -> Page object
|
|
193
|
+
self._console_logs: Dict[str, Any] = {} # tab_id -> page logs
|
|
191
194
|
self._current_tab_id: Optional[str] = None # Current active tab ID
|
|
195
|
+
self.log_limit: int = ConfigLoader.get_max_log_limit() or 1000
|
|
192
196
|
|
|
193
197
|
self.snapshot: Optional[PageSnapshot] = None
|
|
194
198
|
self.executor: Optional[ActionExecutor] = None
|
|
@@ -266,7 +270,7 @@ class HybridBrowserSession:
|
|
|
266
270
|
)
|
|
267
271
|
|
|
268
272
|
# Store in pages dictionary
|
|
269
|
-
self.
|
|
273
|
+
await self._register_new_page(tab_id, new_page)
|
|
270
274
|
|
|
271
275
|
# Navigate if URL provided
|
|
272
276
|
if url:
|
|
@@ -281,6 +285,32 @@ class HybridBrowserSession:
|
|
|
281
285
|
)
|
|
282
286
|
return tab_id
|
|
283
287
|
|
|
288
|
+
async def _register_new_page(self, tab_id: str, new_page: "Page") -> None:
|
|
289
|
+
r"""Register a page and add console event listerers.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
new_page (Page): The new page object to register.
|
|
293
|
+
"""
|
|
294
|
+
# Add new page
|
|
295
|
+
self._pages[tab_id] = new_page
|
|
296
|
+
# Create log for the page
|
|
297
|
+
self._console_logs[tab_id] = deque(maxlen=self.log_limit)
|
|
298
|
+
|
|
299
|
+
# Add event function
|
|
300
|
+
def handle_console_log(msg: ConsoleMessage):
|
|
301
|
+
logs = self._console_logs.get(tab_id)
|
|
302
|
+
if logs is not None:
|
|
303
|
+
logs.append({"type": msg.type, "text": msg.text})
|
|
304
|
+
|
|
305
|
+
# Add event listener for console logs
|
|
306
|
+
new_page.on(event="console", f=handle_console_log)
|
|
307
|
+
|
|
308
|
+
def handle_page_close(page: "Page"):
|
|
309
|
+
self._console_logs.pop(tab_id, None)
|
|
310
|
+
|
|
311
|
+
# Add event listener for cleanup
|
|
312
|
+
new_page.on(event="close", f=handle_page_close)
|
|
313
|
+
|
|
284
314
|
async def register_page(self, new_page: "Page") -> str:
|
|
285
315
|
r"""Register a page that was created externally (e.g., by a click).
|
|
286
316
|
|
|
@@ -297,7 +327,7 @@ class HybridBrowserSession:
|
|
|
297
327
|
|
|
298
328
|
# Create new ID for the page
|
|
299
329
|
tab_id = await TabIdGenerator.generate_tab_id()
|
|
300
|
-
self.
|
|
330
|
+
await self._register_new_page(tab_id, new_page)
|
|
301
331
|
|
|
302
332
|
logger.info(
|
|
303
333
|
f"Registered new tab {tab_id} (opened by user action). "
|
|
@@ -458,6 +488,7 @@ class HybridBrowserSession:
|
|
|
458
488
|
self._context = singleton_instance._context
|
|
459
489
|
self._page = singleton_instance._page
|
|
460
490
|
self._pages = singleton_instance._pages
|
|
491
|
+
self._console_logs = singleton_instance._console_logs
|
|
461
492
|
self._current_tab_id = singleton_instance._current_tab_id
|
|
462
493
|
self.snapshot = singleton_instance.snapshot
|
|
463
494
|
self.executor = singleton_instance.executor
|
|
@@ -502,16 +533,16 @@ class HybridBrowserSession:
|
|
|
502
533
|
self._page = pages[0]
|
|
503
534
|
# Create ID for initial page
|
|
504
535
|
initial_tab_id = await TabIdGenerator.generate_tab_id()
|
|
505
|
-
self.
|
|
536
|
+
await self._register_new_page(initial_tab_id, pages[0])
|
|
506
537
|
self._current_tab_id = initial_tab_id
|
|
507
538
|
# Handle additional pages if any
|
|
508
539
|
for page in pages[1:]:
|
|
509
540
|
tab_id = await TabIdGenerator.generate_tab_id()
|
|
510
|
-
self.
|
|
541
|
+
await self._register_new_page(tab_id, page)
|
|
511
542
|
else:
|
|
512
543
|
self._page = await context.new_page()
|
|
513
544
|
initial_tab_id = await TabIdGenerator.generate_tab_id()
|
|
514
|
-
self.
|
|
545
|
+
await self._register_new_page(initial_tab_id, self._page)
|
|
515
546
|
self._current_tab_id = initial_tab_id
|
|
516
547
|
else:
|
|
517
548
|
self._browser = await self._playwright.chromium.launch(
|
|
@@ -522,7 +553,7 @@ class HybridBrowserSession:
|
|
|
522
553
|
|
|
523
554
|
# Create ID for initial page
|
|
524
555
|
initial_tab_id = await TabIdGenerator.generate_tab_id()
|
|
525
|
-
self.
|
|
556
|
+
await self._register_new_page(initial_tab_id, self._page)
|
|
526
557
|
self._current_tab_id = initial_tab_id
|
|
527
558
|
|
|
528
559
|
# Apply stealth modifications if enabled
|
|
@@ -713,13 +744,19 @@ class HybridBrowserSession:
|
|
|
713
744
|
return f"Navigated to {url}"
|
|
714
745
|
|
|
715
746
|
async def get_snapshot(
|
|
716
|
-
self,
|
|
747
|
+
self,
|
|
748
|
+
*,
|
|
749
|
+
force_refresh: bool = False,
|
|
750
|
+
diff_only: bool = False,
|
|
751
|
+
viewport_limit: bool = False,
|
|
717
752
|
) -> str:
|
|
718
753
|
r"""Get snapshot for current tab."""
|
|
719
754
|
if not self.snapshot:
|
|
720
755
|
return "<empty>"
|
|
721
756
|
return await self.snapshot.capture(
|
|
722
|
-
force_refresh=force_refresh,
|
|
757
|
+
force_refresh=force_refresh,
|
|
758
|
+
diff_only=diff_only,
|
|
759
|
+
viewport_limit=viewport_limit,
|
|
723
760
|
)
|
|
724
761
|
|
|
725
762
|
async def exec_action(self, action: Dict[str, Any]) -> Dict[str, Any]:
|
|
@@ -738,3 +775,13 @@ class HybridBrowserSession:
|
|
|
738
775
|
if self._page is None:
|
|
739
776
|
raise RuntimeError("No active page available")
|
|
740
777
|
return self._page
|
|
778
|
+
|
|
779
|
+
async def get_console_logs(self) -> Dict[str, Any]:
|
|
780
|
+
r"""Get current active logs."""
|
|
781
|
+
await self.ensure_browser()
|
|
782
|
+
if self._current_tab_id is None:
|
|
783
|
+
raise RuntimeError("No active tab available")
|
|
784
|
+
logs = self._console_logs.get(self._current_tab_id, None)
|
|
785
|
+
if logs is None:
|
|
786
|
+
raise RuntimeError("No active logs available for the page")
|
|
787
|
+
return logs
|
|
@@ -40,6 +40,9 @@ class BrowserConfig:
|
|
|
40
40
|
# Default action limits
|
|
41
41
|
DEFAULT_MAX_SCROLL_AMOUNT = 5000 # Maximum scroll distance in pixels
|
|
42
42
|
|
|
43
|
+
# Default config limits
|
|
44
|
+
DEFAULT_MAX_LOG_LIMIT = 1000
|
|
45
|
+
|
|
43
46
|
@staticmethod
|
|
44
47
|
def get_timeout_config() -> Dict[str, int]:
|
|
45
48
|
r"""Get timeout configuration with environment variable support.
|
|
@@ -108,6 +111,22 @@ class BrowserConfig:
|
|
|
108
111
|
),
|
|
109
112
|
}
|
|
110
113
|
|
|
114
|
+
@staticmethod
|
|
115
|
+
def get_log_limits() -> Dict[str, int]:
|
|
116
|
+
r"""Get log limits configuration with environment variable support.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Dict[str, int]: Console Log limits configuration.
|
|
120
|
+
"""
|
|
121
|
+
return {
|
|
122
|
+
'max_log_limit': int(
|
|
123
|
+
os.getenv(
|
|
124
|
+
'HYBRID_BROWSER_MAX_LOG_LIMIT',
|
|
125
|
+
BrowserConfig.DEFAULT_MAX_LOG_LIMIT,
|
|
126
|
+
)
|
|
127
|
+
),
|
|
128
|
+
}
|
|
129
|
+
|
|
111
130
|
@staticmethod
|
|
112
131
|
def get_action_timeout(override: Optional[int] = None) -> int:
|
|
113
132
|
r"""Get action timeout with optional override.
|
|
@@ -178,6 +197,20 @@ class BrowserConfig:
|
|
|
178
197
|
return override
|
|
179
198
|
return BrowserConfig.get_action_limits()['max_scroll_amount']
|
|
180
199
|
|
|
200
|
+
@staticmethod
|
|
201
|
+
def get_max_log_limit(override: Optional[int] = None) -> int:
|
|
202
|
+
r"""Get maximum log limit with optional override.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
override: Optional log limit override value.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
int: Maximum log limit.
|
|
209
|
+
"""
|
|
210
|
+
if override is not None:
|
|
211
|
+
return override
|
|
212
|
+
return BrowserConfig.get_log_limits()['max_log_limit']
|
|
213
|
+
|
|
181
214
|
@staticmethod
|
|
182
215
|
def get_screenshot_timeout(override: Optional[int] = None) -> int:
|
|
183
216
|
r"""Get screenshot timeout with optional override.
|
|
@@ -370,6 +403,11 @@ class ConfigLoader:
|
|
|
370
403
|
r"""Get maximum scroll amount with optional override."""
|
|
371
404
|
return BrowserConfig.get_max_scroll_amount(override)
|
|
372
405
|
|
|
406
|
+
@classmethod
|
|
407
|
+
def get_max_log_limit(cls, override: Optional[int] = None) -> int:
|
|
408
|
+
r"""Get maximum log limit with optional override."""
|
|
409
|
+
return BrowserConfig.get_max_log_limit(override)
|
|
410
|
+
|
|
373
411
|
@classmethod
|
|
374
412
|
def get_screenshot_timeout(cls, override: Optional[int] = None) -> int:
|
|
375
413
|
r"""Get screenshot timeout with optional override."""
|
|
@@ -432,6 +470,11 @@ def get_max_scroll_amount(override: Optional[int] = None) -> int:
|
|
|
432
470
|
return BrowserConfig.get_max_scroll_amount(override)
|
|
433
471
|
|
|
434
472
|
|
|
473
|
+
def get_max_log_limit(override: Optional[int] = None) -> int:
|
|
474
|
+
r"""Get maximum log limit with optional override."""
|
|
475
|
+
return BrowserConfig.get_max_log_limit(override)
|
|
476
|
+
|
|
477
|
+
|
|
435
478
|
def get_screenshot_timeout(override: Optional[int] = None) -> int:
|
|
436
479
|
r"""Get screenshot timeout with optional override."""
|
|
437
480
|
return BrowserConfig.get_screenshot_timeout(override)
|