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.
Files changed (173) hide show
  1. camel/__init__.py +1 -1
  2. camel/agents/_utils.py +38 -0
  3. camel/agents/chat_agent.py +2217 -519
  4. camel/agents/mcp_agent.py +30 -27
  5. camel/configs/__init__.py +15 -0
  6. camel/configs/aihubmix_config.py +88 -0
  7. camel/configs/amd_config.py +70 -0
  8. camel/configs/cometapi_config.py +104 -0
  9. camel/configs/minimax_config.py +93 -0
  10. camel/configs/nebius_config.py +103 -0
  11. camel/data_collectors/alpaca_collector.py +15 -6
  12. camel/datasets/base_generator.py +39 -10
  13. camel/environments/single_step.py +28 -3
  14. camel/environments/tic_tac_toe.py +1 -1
  15. camel/interpreters/__init__.py +2 -0
  16. camel/interpreters/docker/Dockerfile +3 -12
  17. camel/interpreters/e2b_interpreter.py +34 -1
  18. camel/interpreters/microsandbox_interpreter.py +395 -0
  19. camel/loaders/__init__.py +11 -2
  20. camel/loaders/chunkr_reader.py +9 -0
  21. camel/memories/agent_memories.py +48 -4
  22. camel/memories/base.py +26 -0
  23. camel/memories/blocks/chat_history_block.py +122 -4
  24. camel/memories/context_creators/score_based.py +25 -384
  25. camel/memories/records.py +88 -8
  26. camel/messages/base.py +153 -34
  27. camel/models/__init__.py +10 -0
  28. camel/models/aihubmix_model.py +83 -0
  29. camel/models/aiml_model.py +1 -16
  30. camel/models/amd_model.py +101 -0
  31. camel/models/anthropic_model.py +6 -19
  32. camel/models/aws_bedrock_model.py +2 -33
  33. camel/models/azure_openai_model.py +114 -89
  34. camel/models/base_audio_model.py +3 -1
  35. camel/models/base_model.py +32 -14
  36. camel/models/cohere_model.py +1 -16
  37. camel/models/cometapi_model.py +83 -0
  38. camel/models/crynux_model.py +1 -16
  39. camel/models/deepseek_model.py +1 -16
  40. camel/models/fish_audio_model.py +6 -0
  41. camel/models/gemini_model.py +36 -18
  42. camel/models/groq_model.py +1 -17
  43. camel/models/internlm_model.py +1 -16
  44. camel/models/litellm_model.py +1 -16
  45. camel/models/lmstudio_model.py +1 -17
  46. camel/models/minimax_model.py +83 -0
  47. camel/models/mistral_model.py +1 -16
  48. camel/models/model_factory.py +27 -1
  49. camel/models/modelscope_model.py +1 -16
  50. camel/models/moonshot_model.py +105 -24
  51. camel/models/nebius_model.py +83 -0
  52. camel/models/nemotron_model.py +0 -5
  53. camel/models/netmind_model.py +1 -16
  54. camel/models/novita_model.py +1 -16
  55. camel/models/nvidia_model.py +1 -16
  56. camel/models/ollama_model.py +4 -19
  57. camel/models/openai_compatible_model.py +62 -41
  58. camel/models/openai_model.py +62 -57
  59. camel/models/openrouter_model.py +1 -17
  60. camel/models/ppio_model.py +1 -16
  61. camel/models/qianfan_model.py +1 -16
  62. camel/models/qwen_model.py +1 -16
  63. camel/models/reka_model.py +1 -16
  64. camel/models/samba_model.py +34 -47
  65. camel/models/sglang_model.py +64 -31
  66. camel/models/siliconflow_model.py +1 -16
  67. camel/models/stub_model.py +0 -4
  68. camel/models/togetherai_model.py +1 -16
  69. camel/models/vllm_model.py +1 -16
  70. camel/models/volcano_model.py +0 -17
  71. camel/models/watsonx_model.py +1 -16
  72. camel/models/yi_model.py +1 -16
  73. camel/models/zhipuai_model.py +60 -16
  74. camel/parsers/__init__.py +18 -0
  75. camel/parsers/mcp_tool_call_parser.py +176 -0
  76. camel/retrievers/auto_retriever.py +1 -0
  77. camel/runtimes/daytona_runtime.py +11 -12
  78. camel/societies/__init__.py +2 -0
  79. camel/societies/workforce/__init__.py +2 -0
  80. camel/societies/workforce/events.py +122 -0
  81. camel/societies/workforce/prompts.py +146 -66
  82. camel/societies/workforce/role_playing_worker.py +15 -11
  83. camel/societies/workforce/single_agent_worker.py +302 -65
  84. camel/societies/workforce/structured_output_handler.py +30 -18
  85. camel/societies/workforce/task_channel.py +163 -27
  86. camel/societies/workforce/utils.py +107 -13
  87. camel/societies/workforce/workflow_memory_manager.py +772 -0
  88. camel/societies/workforce/workforce.py +1949 -579
  89. camel/societies/workforce/workforce_callback.py +74 -0
  90. camel/societies/workforce/workforce_logger.py +168 -145
  91. camel/societies/workforce/workforce_metrics.py +33 -0
  92. camel/storages/key_value_storages/json.py +15 -2
  93. camel/storages/key_value_storages/mem0_cloud.py +48 -47
  94. camel/storages/object_storages/google_cloud.py +1 -1
  95. camel/storages/vectordb_storages/oceanbase.py +13 -13
  96. camel/storages/vectordb_storages/qdrant.py +3 -3
  97. camel/storages/vectordb_storages/tidb.py +8 -6
  98. camel/tasks/task.py +4 -3
  99. camel/toolkits/__init__.py +20 -7
  100. camel/toolkits/aci_toolkit.py +45 -0
  101. camel/toolkits/base.py +6 -4
  102. camel/toolkits/code_execution.py +28 -1
  103. camel/toolkits/context_summarizer_toolkit.py +684 -0
  104. camel/toolkits/dappier_toolkit.py +5 -1
  105. camel/toolkits/dingtalk.py +1135 -0
  106. camel/toolkits/edgeone_pages_mcp_toolkit.py +11 -31
  107. camel/toolkits/excel_toolkit.py +1 -1
  108. camel/toolkits/{file_write_toolkit.py → file_toolkit.py} +430 -36
  109. camel/toolkits/function_tool.py +13 -3
  110. camel/toolkits/github_toolkit.py +104 -17
  111. camel/toolkits/gmail_toolkit.py +1839 -0
  112. camel/toolkits/google_calendar_toolkit.py +38 -4
  113. camel/toolkits/google_drive_mcp_toolkit.py +12 -31
  114. camel/toolkits/hybrid_browser_toolkit/config_loader.py +15 -0
  115. camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit.py +77 -8
  116. camel/toolkits/hybrid_browser_toolkit/hybrid_browser_toolkit_ts.py +884 -88
  117. camel/toolkits/hybrid_browser_toolkit/installer.py +203 -0
  118. camel/toolkits/hybrid_browser_toolkit/ts/package-lock.json +5 -612
  119. camel/toolkits/hybrid_browser_toolkit/ts/package.json +0 -1
  120. camel/toolkits/hybrid_browser_toolkit/ts/src/browser-session.ts +959 -89
  121. camel/toolkits/hybrid_browser_toolkit/ts/src/config-loader.ts +9 -2
  122. camel/toolkits/hybrid_browser_toolkit/ts/src/hybrid-browser-toolkit.ts +281 -213
  123. camel/toolkits/hybrid_browser_toolkit/ts/src/parent-child-filter.ts +226 -0
  124. camel/toolkits/hybrid_browser_toolkit/ts/src/snapshot-parser.ts +219 -0
  125. camel/toolkits/hybrid_browser_toolkit/ts/src/som-screenshot-injected.ts +543 -0
  126. camel/toolkits/hybrid_browser_toolkit/ts/src/types.ts +23 -3
  127. camel/toolkits/hybrid_browser_toolkit/ts/websocket-server.js +72 -7
  128. camel/toolkits/hybrid_browser_toolkit/ws_wrapper.py +582 -132
  129. camel/toolkits/hybrid_browser_toolkit_py/actions.py +158 -0
  130. camel/toolkits/hybrid_browser_toolkit_py/browser_session.py +55 -8
  131. camel/toolkits/hybrid_browser_toolkit_py/config_loader.py +43 -0
  132. camel/toolkits/hybrid_browser_toolkit_py/hybrid_browser_toolkit.py +321 -8
  133. camel/toolkits/hybrid_browser_toolkit_py/snapshot.py +10 -4
  134. camel/toolkits/hybrid_browser_toolkit_py/unified_analyzer.js +45 -4
  135. camel/toolkits/{openai_image_toolkit.py → image_generation_toolkit.py} +151 -53
  136. camel/toolkits/klavis_toolkit.py +5 -1
  137. camel/toolkits/markitdown_toolkit.py +27 -1
  138. camel/toolkits/math_toolkit.py +64 -10
  139. camel/toolkits/mcp_toolkit.py +366 -71
  140. camel/toolkits/memory_toolkit.py +5 -1
  141. camel/toolkits/message_integration.py +18 -13
  142. camel/toolkits/minimax_mcp_toolkit.py +195 -0
  143. camel/toolkits/note_taking_toolkit.py +19 -10
  144. camel/toolkits/notion_mcp_toolkit.py +16 -26
  145. camel/toolkits/openbb_toolkit.py +5 -1
  146. camel/toolkits/origene_mcp_toolkit.py +8 -49
  147. camel/toolkits/playwright_mcp_toolkit.py +12 -31
  148. camel/toolkits/resend_toolkit.py +168 -0
  149. camel/toolkits/search_toolkit.py +264 -91
  150. camel/toolkits/slack_toolkit.py +64 -10
  151. camel/toolkits/terminal_toolkit/__init__.py +18 -0
  152. camel/toolkits/terminal_toolkit/terminal_toolkit.py +957 -0
  153. camel/toolkits/terminal_toolkit/utils.py +532 -0
  154. camel/toolkits/vertex_ai_veo_toolkit.py +590 -0
  155. camel/toolkits/video_analysis_toolkit.py +17 -11
  156. camel/toolkits/wechat_official_toolkit.py +483 -0
  157. camel/toolkits/zapier_toolkit.py +5 -1
  158. camel/types/__init__.py +2 -2
  159. camel/types/enums.py +274 -7
  160. camel/types/openai_types.py +2 -2
  161. camel/types/unified_model_type.py +15 -0
  162. camel/utils/commons.py +36 -5
  163. camel/utils/constants.py +3 -0
  164. camel/utils/context_utils.py +1003 -0
  165. camel/utils/mcp.py +138 -4
  166. camel/utils/token_counting.py +43 -20
  167. {camel_ai-0.2.73a4.dist-info → camel_ai-0.2.80a2.dist-info}/METADATA +223 -83
  168. {camel_ai-0.2.73a4.dist-info → camel_ai-0.2.80a2.dist-info}/RECORD +170 -141
  169. camel/loaders/pandas_reader.py +0 -368
  170. camel/toolkits/openai_agent_toolkit.py +0 -135
  171. camel/toolkits/terminal_toolkit.py +0 -1550
  172. {camel_ai-0.2.73a4.dist-info → camel_ai-0.2.80a2.dist-info}/WHEEL +0 -0
  173. {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._pages[tab_id] = new_page
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._pages[tab_id] = new_page
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._pages[initial_tab_id] = pages[0]
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._pages[tab_id] = page
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._pages[initial_tab_id] = self._page
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._pages[initial_tab_id] = self._page
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, *, force_refresh: bool = False, diff_only: bool = False
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, diff_only=diff_only
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)