vibesurf 0.1.35__py3-none-any.whl → 0.1.37__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.
- vibe_surf/_version.py +2 -2
- vibe_surf/agents/browser_use_agent.py +14 -276
- vibe_surf/agents/report_writer_agent.py +21 -1
- vibe_surf/agents/vibe_surf_agent.py +61 -2
- vibe_surf/backend/llm_config.py +27 -0
- vibe_surf/backend/shared_state.py +26 -26
- vibe_surf/backend/utils/encryption.py +40 -4
- vibe_surf/backend/utils/llm_factory.py +16 -0
- vibe_surf/browser/agen_browser_profile.py +5 -0
- vibe_surf/browser/agent_browser_session.py +116 -25
- vibe_surf/browser/watchdogs/action_watchdog.py +1 -83
- vibe_surf/browser/watchdogs/dom_watchdog.py +9 -6
- vibe_surf/cli.py +52 -4
- vibe_surf/llm/openai_compatible.py +2 -9
- vibe_surf/telemetry/views.py +32 -0
- vibe_surf/tools/browser_use_tools.py +39 -42
- vibe_surf/tools/file_system.py +5 -2
- vibe_surf/tools/utils.py +118 -0
- vibe_surf/tools/vibesurf_tools.py +44 -236
- vibe_surf/tools/views.py +1 -1
- {vibesurf-0.1.35.dist-info → vibesurf-0.1.37.dist-info}/METADATA +12 -2
- {vibesurf-0.1.35.dist-info → vibesurf-0.1.37.dist-info}/RECORD +26 -25
- {vibesurf-0.1.35.dist-info → vibesurf-0.1.37.dist-info}/WHEEL +0 -0
- {vibesurf-0.1.35.dist-info → vibesurf-0.1.37.dist-info}/entry_points.txt +0 -0
- {vibesurf-0.1.35.dist-info → vibesurf-0.1.37.dist-info}/licenses/LICENSE +0 -0
- {vibesurf-0.1.35.dist-info → vibesurf-0.1.37.dist-info}/top_level.txt +0 -0
|
@@ -177,6 +177,22 @@ def create_llm_from_profile(llm_profile) -> BaseChatModel:
|
|
|
177
177
|
params["region_name"] = provider_config["region_name"]
|
|
178
178
|
return ChatAnthropicBedrock(**params)
|
|
179
179
|
|
|
180
|
+
elif provider == "qwen":
|
|
181
|
+
return ChatOpenAICompatible(
|
|
182
|
+
model=model,
|
|
183
|
+
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1" or base_url,
|
|
184
|
+
api_key=api_key,
|
|
185
|
+
**common_params
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
elif provider == "kimi":
|
|
189
|
+
return ChatOpenAICompatible(
|
|
190
|
+
model=model,
|
|
191
|
+
base_url="https://api.moonshot.cn/v1" or base_url,
|
|
192
|
+
api_key=api_key,
|
|
193
|
+
**common_params
|
|
194
|
+
)
|
|
195
|
+
|
|
180
196
|
elif provider == "openai_compatible":
|
|
181
197
|
if not base_url:
|
|
182
198
|
raise ValueError("OpenAI Compatible provider requires base_url")
|
|
@@ -45,6 +45,11 @@ class AgentBrowserProfile(BrowserProfile):
|
|
|
45
45
|
'id': 'edibdbjcniadpccecjdfdjjppcpchdlm',
|
|
46
46
|
'url': 'https://clients2.google.com/service/update2/crx?response=redirect&prodversion=130&acceptformat=crx3&x=id%3Dedibdbjcniadpccecjdfdjjppcpchdlm%26uc',
|
|
47
47
|
},
|
|
48
|
+
{
|
|
49
|
+
'name': 'Force Background Tab',
|
|
50
|
+
'id': 'gidlfommnbibbmegmgajdbikelkdcmcl',
|
|
51
|
+
'url': 'https://clients2.google.com/service/update2/crx?response=redirect&prodversion=133&acceptformat=crx3&x=id%3Dgidlfommnbibbmegmgajdbikelkdcmcl%26uc',
|
|
52
|
+
},
|
|
48
53
|
# {
|
|
49
54
|
# 'name': 'ClearURLs',
|
|
50
55
|
# 'id': 'lckanjgmijmafbedllaakclkaicjfmnk',
|
|
@@ -4,7 +4,7 @@ import asyncio
|
|
|
4
4
|
import os
|
|
5
5
|
import pdb
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Any,
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Literal, Self, Union, cast, Optional
|
|
8
8
|
|
|
9
9
|
from browser_use.browser.session import BrowserSession, CDPSession
|
|
10
10
|
from pydantic import Field
|
|
@@ -68,7 +68,7 @@ class AgentBrowserSession(BrowserSession):
|
|
|
68
68
|
executable_path: str | Path | None = None,
|
|
69
69
|
headless: bool | None = None,
|
|
70
70
|
args: list[str] | None = None,
|
|
71
|
-
ignore_default_args: list[str] |
|
|
71
|
+
ignore_default_args: list[str] | Literal[True] | None = None,
|
|
72
72
|
channel: str | None = None,
|
|
73
73
|
chromium_sandbox: bool | None = None,
|
|
74
74
|
devtools: bool | None = None,
|
|
@@ -86,11 +86,15 @@ class AgentBrowserSession(BrowserSession):
|
|
|
86
86
|
record_har_mode: str | None = None,
|
|
87
87
|
record_har_path: str | Path | None = None,
|
|
88
88
|
record_video_dir: str | Path | None = None,
|
|
89
|
+
record_video_framerate: int | None = None,
|
|
90
|
+
record_video_size: dict | None = None,
|
|
89
91
|
# From BrowserLaunchPersistentContextArgs
|
|
90
92
|
user_data_dir: str | Path | None = None,
|
|
91
93
|
# From BrowserNewContextArgs
|
|
92
94
|
storage_state: str | Path | dict[str, Any] | None = None,
|
|
93
95
|
# BrowserProfile specific fields
|
|
96
|
+
use_cloud: bool | None = None,
|
|
97
|
+
cloud_browser: bool | None = None, # Backward compatibility alias
|
|
94
98
|
disable_security: bool | None = None,
|
|
95
99
|
deterministic_rendering: bool | None = None,
|
|
96
100
|
allowed_domains: list[str] | None = None,
|
|
@@ -99,15 +103,21 @@ class AgentBrowserSession(BrowserSession):
|
|
|
99
103
|
enable_default_extensions: bool | None = None,
|
|
100
104
|
window_size: dict | None = None,
|
|
101
105
|
window_position: dict | None = None,
|
|
102
|
-
cross_origin_iframes: bool | None = None,
|
|
103
106
|
minimum_wait_page_load_time: float | None = None,
|
|
104
107
|
wait_for_network_idle_page_load_time: float | None = None,
|
|
105
108
|
wait_between_actions: float | None = None,
|
|
106
|
-
highlight_elements: bool | None = None,
|
|
107
109
|
filter_highlight_ids: bool | None = None,
|
|
108
110
|
auto_download_pdfs: bool | None = None,
|
|
109
111
|
profile_directory: str | None = None,
|
|
110
112
|
cookie_whitelist_domains: list[str] | None = None,
|
|
113
|
+
# DOM extraction layer configuration
|
|
114
|
+
cross_origin_iframes: bool | None = None,
|
|
115
|
+
highlight_elements: bool | None = None,
|
|
116
|
+
dom_highlight_elements: bool | None = None,
|
|
117
|
+
paint_order_filtering: bool | None = None,
|
|
118
|
+
# Iframe processing limits
|
|
119
|
+
max_iframes: int | None = None,
|
|
120
|
+
max_iframe_depth: int | None = None,
|
|
111
121
|
# AgentBrowserProfile specific fields
|
|
112
122
|
custom_extensions: list[str] | None = None,
|
|
113
123
|
):
|
|
@@ -585,17 +595,19 @@ class AgentBrowserSession(BrowserSession):
|
|
|
585
595
|
f'🔍 DOMWatchdog.on_BrowserStateRequestEvent: Network waiting failed: {e}, continuing anyway...'
|
|
586
596
|
)
|
|
587
597
|
|
|
588
|
-
async def take_screenshot(self, target_id: Optional[str] = None,
|
|
598
|
+
async def take_screenshot(self, target_id: Optional[str] = None,
|
|
599
|
+
path: str | None = None,
|
|
600
|
+
full_page: bool = False,
|
|
601
|
+
format: str = 'png',
|
|
602
|
+
quality: int | None = None,
|
|
603
|
+
clip: dict | None = None,
|
|
604
|
+
) -> bytes:
|
|
589
605
|
"""
|
|
590
606
|
Concurrent screenshot method that bypasses serial bottlenecks in ScreenshotWatchdog.
|
|
591
607
|
|
|
592
608
|
This method performs direct CDP calls for maximum concurrency.
|
|
593
609
|
"""
|
|
594
|
-
|
|
595
|
-
if not self.agent_focus:
|
|
596
|
-
self.logger.warning('No page focus to get html, please specify a target id.')
|
|
597
|
-
return ''
|
|
598
|
-
target_id = self.agent_focus.target_id
|
|
610
|
+
|
|
599
611
|
cdp_session = await self.get_or_create_cdp_session(target_id, focus=False)
|
|
600
612
|
await self._wait_for_stable_network()
|
|
601
613
|
|
|
@@ -607,13 +619,98 @@ class AgentBrowserSession(BrowserSession):
|
|
|
607
619
|
pass
|
|
608
620
|
|
|
609
621
|
try:
|
|
622
|
+
import base64
|
|
610
623
|
from cdp_use.cdp.page import CaptureScreenshotParameters
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
624
|
+
|
|
625
|
+
# Build parameters dict explicitly to satisfy TypedDict expectations
|
|
626
|
+
params: CaptureScreenshotParameters = {
|
|
627
|
+
'format': format,
|
|
628
|
+
'captureBeyondViewport': full_page,
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
if quality is not None and format == 'jpeg':
|
|
632
|
+
params['quality'] = quality
|
|
633
|
+
|
|
634
|
+
if clip:
|
|
635
|
+
params['clip'] = {
|
|
636
|
+
'x': clip['x'],
|
|
637
|
+
'y': clip['y'],
|
|
638
|
+
'width': clip['width'],
|
|
639
|
+
'height': clip['height'],
|
|
640
|
+
'scale': 1,
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
params = CaptureScreenshotParameters(**params)
|
|
644
|
+
|
|
645
|
+
result = await cdp_session.cdp_client.send.Page.captureScreenshot(params=params,
|
|
646
|
+
session_id=cdp_session.session_id)
|
|
647
|
+
|
|
648
|
+
if not result or 'data' not in result:
|
|
649
|
+
raise Exception('Screenshot failed - no data returned')
|
|
650
|
+
|
|
651
|
+
screenshot_data = base64.b64decode(result['data'])
|
|
652
|
+
|
|
653
|
+
if path:
|
|
654
|
+
Path(path).write_bytes(screenshot_data)
|
|
655
|
+
|
|
656
|
+
return screenshot_data
|
|
657
|
+
|
|
658
|
+
except Exception as e:
|
|
659
|
+
self.logger.error(f'Concurrent screenshot failed: {type(e).__name__}: {e}')
|
|
660
|
+
raise
|
|
661
|
+
|
|
662
|
+
async def take_screenshot_base64(self, target_id: Optional[str] = None,
|
|
663
|
+
full_page: bool = False,
|
|
664
|
+
format: str = 'png',
|
|
665
|
+
quality: int | None = None,
|
|
666
|
+
clip: dict | None = None,
|
|
667
|
+
) -> str:
|
|
668
|
+
"""
|
|
669
|
+
Concurrent screenshot method that bypasses serial bottlenecks in ScreenshotWatchdog.
|
|
670
|
+
|
|
671
|
+
This method performs direct CDP calls for maximum concurrency.
|
|
672
|
+
"""
|
|
673
|
+
|
|
674
|
+
cdp_session = await self.get_or_create_cdp_session(target_id, focus=False)
|
|
675
|
+
await self._wait_for_stable_network()
|
|
676
|
+
|
|
677
|
+
try:
|
|
678
|
+
ready_state = await cdp_session.cdp_client.send.Runtime.evaluate(
|
|
679
|
+
params={'expression': 'document.readyState'}, session_id=cdp_session.session_id
|
|
616
680
|
)
|
|
681
|
+
except Exception:
|
|
682
|
+
pass
|
|
683
|
+
|
|
684
|
+
try:
|
|
685
|
+
import base64
|
|
686
|
+
from cdp_use.cdp.page import CaptureScreenshotParameters
|
|
687
|
+
|
|
688
|
+
# Build parameters dict explicitly to satisfy TypedDict expectations
|
|
689
|
+
params: CaptureScreenshotParameters = {
|
|
690
|
+
'format': format,
|
|
691
|
+
'captureBeyondViewport': full_page,
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
if quality is not None and format == 'jpeg':
|
|
695
|
+
params['quality'] = quality
|
|
696
|
+
|
|
697
|
+
if clip:
|
|
698
|
+
params['clip'] = {
|
|
699
|
+
'x': clip['x'],
|
|
700
|
+
'y': clip['y'],
|
|
701
|
+
'width': clip['width'],
|
|
702
|
+
'height': clip['height'],
|
|
703
|
+
'scale': 1,
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
params = CaptureScreenshotParameters(**params)
|
|
707
|
+
|
|
708
|
+
result = await cdp_session.cdp_client.send.Page.captureScreenshot(params=params,
|
|
709
|
+
session_id=cdp_session.session_id)
|
|
710
|
+
|
|
711
|
+
if not result or 'data' not in result:
|
|
712
|
+
raise Exception('Screenshot failed - no data returned')
|
|
713
|
+
|
|
617
714
|
return result['data']
|
|
618
715
|
|
|
619
716
|
except Exception as e:
|
|
@@ -625,12 +722,8 @@ class AgentBrowserSession(BrowserSession):
|
|
|
625
722
|
Get html content of current page
|
|
626
723
|
:return:
|
|
627
724
|
"""
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
self.logger.warning('No page focus to get html, please specify a target id.')
|
|
631
|
-
return ''
|
|
632
|
-
target_id = self.agent_focus.target_id
|
|
633
|
-
cdp_session = await self.get_or_create_cdp_session(target_id, focus=True)
|
|
725
|
+
|
|
726
|
+
cdp_session = await self.get_or_create_cdp_session(target_id, focus=False)
|
|
634
727
|
await self._wait_for_stable_network()
|
|
635
728
|
|
|
636
729
|
try:
|
|
@@ -654,7 +747,6 @@ class AgentBrowserSession(BrowserSession):
|
|
|
654
747
|
|
|
655
748
|
async def get_browser_state_summary(
|
|
656
749
|
self,
|
|
657
|
-
cache_clickable_elements_hashes: bool = True,
|
|
658
750
|
include_screenshot: bool = True,
|
|
659
751
|
cached: bool = False,
|
|
660
752
|
include_recent_events: bool = False,
|
|
@@ -677,7 +769,6 @@ class AgentBrowserSession(BrowserSession):
|
|
|
677
769
|
browser_state = await self._dom_watchdog.get_browser_state_no_event_bus(
|
|
678
770
|
include_dom=True,
|
|
679
771
|
include_screenshot=include_screenshot,
|
|
680
|
-
cache_clickable_elements_hashes=cache_clickable_elements_hashes,
|
|
681
772
|
include_recent_events=include_recent_events
|
|
682
773
|
)
|
|
683
774
|
return browser_state
|
|
@@ -738,9 +829,9 @@ class AgentBrowserSession(BrowserSession):
|
|
|
738
829
|
|
|
739
830
|
return tabs
|
|
740
831
|
|
|
741
|
-
async def refresh_page(self):
|
|
742
|
-
cdp_session = await self.browser_session.get_or_create_cdp_session()
|
|
832
|
+
async def refresh_page(self, target_id: Optional[str] = None, ):
|
|
743
833
|
try:
|
|
834
|
+
cdp_session = await self.browser_session.get_or_create_cdp_session(target_id)
|
|
744
835
|
# Reload the target
|
|
745
836
|
await cdp_session.cdp_client.send.Page.reload(session_id=cdp_session.session_id)
|
|
746
837
|
|
|
@@ -20,86 +20,4 @@ from browser_use.browser.watchdog_base import BaseWatchdog
|
|
|
20
20
|
from browser_use.dom.service import EnhancedDOMTreeNode
|
|
21
21
|
|
|
22
22
|
class CustomActionWatchdog(DefaultActionWatchdog):
|
|
23
|
-
|
|
24
|
-
"""Handle click request with CDP."""
|
|
25
|
-
try:
|
|
26
|
-
# Check if session is alive before attempting any operations
|
|
27
|
-
if not self.browser_session.agent_focus or not self.browser_session.agent_focus.target_id:
|
|
28
|
-
error_msg = 'Cannot execute click: browser session is corrupted (target_id=None). Session may have crashed.'
|
|
29
|
-
self.logger.error(f'⚠️ {error_msg}')
|
|
30
|
-
raise BrowserError(error_msg)
|
|
31
|
-
|
|
32
|
-
# Use the provided node
|
|
33
|
-
element_node = event.node
|
|
34
|
-
index_for_logging = element_node.element_index or 'unknown'
|
|
35
|
-
starting_target_id = self.browser_session.agent_focus.target_id
|
|
36
|
-
|
|
37
|
-
# Track initial number of tabs to detect new tab opening
|
|
38
|
-
if hasattr(self.browser_session, "main_browser_session") and self.browser_session.main_browser_session:
|
|
39
|
-
initial_target_ids = await self.browser_session.main_browser_session._cdp_get_all_pages()
|
|
40
|
-
else:
|
|
41
|
-
initial_target_ids = await self.browser_session._cdp_get_all_pages()
|
|
42
|
-
|
|
43
|
-
# Check if element is a file input (should not be clicked)
|
|
44
|
-
if self.browser_session.is_file_input(element_node):
|
|
45
|
-
msg = f'Index {index_for_logging} - has an element which opens file upload dialog. To upload files please use a specific function to upload files'
|
|
46
|
-
self.logger.info(msg)
|
|
47
|
-
raise BrowserError(
|
|
48
|
-
message=msg,
|
|
49
|
-
long_term_memory=msg,
|
|
50
|
-
)
|
|
51
|
-
|
|
52
|
-
# Perform the actual click using internal implementation
|
|
53
|
-
click_metadata = None
|
|
54
|
-
click_metadata = await self._click_element_node_impl(element_node,
|
|
55
|
-
while_holding_ctrl=event.while_holding_ctrl)
|
|
56
|
-
download_path = None # moved to downloads_watchdog.py
|
|
57
|
-
|
|
58
|
-
# Build success message
|
|
59
|
-
if download_path:
|
|
60
|
-
msg = f'Downloaded file to {download_path}'
|
|
61
|
-
self.logger.info(f'💾 {msg}')
|
|
62
|
-
else:
|
|
63
|
-
msg = f'Clicked button with index {index_for_logging}: {element_node.get_all_children_text(max_depth=2)}'
|
|
64
|
-
self.logger.debug(f'🖱️ {msg}')
|
|
65
|
-
self.logger.debug(f'Element xpath: {element_node.xpath}')
|
|
66
|
-
|
|
67
|
-
# Wait a bit for potential new tab to be created
|
|
68
|
-
# This is necessary because tab creation is async and might not be immediate
|
|
69
|
-
await asyncio.sleep(0.5)
|
|
70
|
-
|
|
71
|
-
# Clear cached state after click action since DOM might have changed
|
|
72
|
-
self.browser_session.agent_focus = await self.browser_session.get_or_create_cdp_session(
|
|
73
|
-
target_id=starting_target_id, focus=True
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
# Check if a new tab was opened
|
|
77
|
-
if hasattr(self.browser_session, "main_browser_session") and self.browser_session.main_browser_session:
|
|
78
|
-
after_target_ids = await self.browser_session.main_browser_session._cdp_get_all_pages()
|
|
79
|
-
else:
|
|
80
|
-
after_target_ids = await self.browser_session._cdp_get_all_pages()
|
|
81
|
-
new_target_ids = {t['targetId'] for t in after_target_ids} - {t['targetId'] for t in initial_target_ids}
|
|
82
|
-
if new_target_ids:
|
|
83
|
-
new_tab_msg = 'New tab opened - switching to it'
|
|
84
|
-
msg += f' - {new_tab_msg}'
|
|
85
|
-
self.logger.info(f'🔗 {new_tab_msg}')
|
|
86
|
-
new_target_id = new_target_ids.pop()
|
|
87
|
-
if not event.while_holding_ctrl:
|
|
88
|
-
# if while_holding_ctrl=False it means agent was not expecting a new tab to be opened
|
|
89
|
-
# so we need to switch to the new tab to make the agent aware of the surprise new tab that was opened.
|
|
90
|
-
# when while_holding_ctrl=True we dont actually want to switch to it,
|
|
91
|
-
# we should match human expectations of ctrl+click which opens in the background,
|
|
92
|
-
# so in multi_act it usually already sends [click_element_by_index(123, while_holding_ctrl=True), switch_tab(tab_id=None)] anyway
|
|
93
|
-
from browser_use.browser.events import SwitchTabEvent
|
|
94
|
-
|
|
95
|
-
await self.browser_session.get_or_create_cdp_session(
|
|
96
|
-
target_id=new_target_id, focus=True
|
|
97
|
-
)
|
|
98
|
-
else:
|
|
99
|
-
await self.browser_session.get_or_create_cdp_session(
|
|
100
|
-
target_id=new_target_id, focus=False
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
return None
|
|
104
|
-
except Exception as e:
|
|
105
|
-
raise
|
|
23
|
+
pass
|
|
@@ -27,7 +27,6 @@ class CustomDOMWatchdog(DOMWatchdog):
|
|
|
27
27
|
|
|
28
28
|
async def get_browser_state_no_event_bus(self, include_dom: bool = True,
|
|
29
29
|
include_screenshot: bool = True,
|
|
30
|
-
cache_clickable_elements_hashes: bool = True,
|
|
31
30
|
include_recent_events: bool = False) -> 'BrowserStateSummary':
|
|
32
31
|
"""Handle browser state request by coordinating DOM building and screenshot capture.
|
|
33
32
|
|
|
@@ -91,7 +90,7 @@ class CustomDOMWatchdog(DOMWatchdog):
|
|
|
91
90
|
# Start clean screenshot task if requested (without JS highlights)
|
|
92
91
|
if include_screenshot:
|
|
93
92
|
self.logger.debug('🔍 DOMWatchdog.on_BrowserStateRequestEvent: 📸 Starting clean screenshot task...')
|
|
94
|
-
screenshot_task = asyncio.create_task(self.browser_session.
|
|
93
|
+
screenshot_task = asyncio.create_task(self.browser_session.take_screenshot_base64())
|
|
95
94
|
|
|
96
95
|
# Wait for both tasks to complete
|
|
97
96
|
content = None
|
|
@@ -121,13 +120,18 @@ class CustomDOMWatchdog(DOMWatchdog):
|
|
|
121
120
|
try:
|
|
122
121
|
self.logger.debug(
|
|
123
122
|
'🔍 DOMWatchdog.on_BrowserStateRequestEvent: 🎨 Applying Python-based highlighting...')
|
|
124
|
-
from
|
|
123
|
+
from browser_use.browser.python_highlights import create_highlighted_screenshot_async
|
|
125
124
|
|
|
126
125
|
# Get CDP session for viewport info
|
|
127
126
|
cdp_session = await self.browser_session.get_or_create_cdp_session()
|
|
128
127
|
|
|
129
|
-
screenshot_b64 = await create_highlighted_screenshot_async(
|
|
130
|
-
|
|
128
|
+
screenshot_b64 = await create_highlighted_screenshot_async(
|
|
129
|
+
screenshot_b64,
|
|
130
|
+
content.selector_map,
|
|
131
|
+
cdp_session,
|
|
132
|
+
self.browser_session.browser_profile.filter_highlight_ids,
|
|
133
|
+
)
|
|
134
|
+
|
|
131
135
|
self.logger.debug(
|
|
132
136
|
f'🔍 DOMWatchdog.on_BrowserStateRequestEvent: ✅ Applied highlights to {len(content.selector_map)} elements'
|
|
133
137
|
)
|
|
@@ -234,4 +238,3 @@ class CustomDOMWatchdog(DOMWatchdog):
|
|
|
234
238
|
is_pdf_viewer=False,
|
|
235
239
|
recent_events=None,
|
|
236
240
|
)
|
|
237
|
-
|
vibe_surf/cli.py
CHANGED
|
@@ -12,6 +12,7 @@ import socket
|
|
|
12
12
|
import platform
|
|
13
13
|
import time
|
|
14
14
|
import importlib.util
|
|
15
|
+
import argparse
|
|
15
16
|
from pathlib import Path
|
|
16
17
|
from typing import Optional
|
|
17
18
|
import os
|
|
@@ -289,6 +290,26 @@ def select_browser() -> Optional[str]:
|
|
|
289
290
|
return None
|
|
290
291
|
|
|
291
292
|
|
|
293
|
+
def find_first_available_browser() -> Optional[str]:
|
|
294
|
+
"""Find the first available browser in order: Chrome, Edge, Brave."""
|
|
295
|
+
# Try Chrome first
|
|
296
|
+
chrome_path = find_chrome_browser()
|
|
297
|
+
if chrome_path:
|
|
298
|
+
return chrome_path
|
|
299
|
+
|
|
300
|
+
# Try Edge second
|
|
301
|
+
edge_path = find_edge_browser()
|
|
302
|
+
if edge_path:
|
|
303
|
+
return edge_path
|
|
304
|
+
|
|
305
|
+
# Try Brave third
|
|
306
|
+
brave_path = find_brave_browser()
|
|
307
|
+
if brave_path:
|
|
308
|
+
return brave_path
|
|
309
|
+
|
|
310
|
+
return None
|
|
311
|
+
|
|
312
|
+
|
|
292
313
|
def configure_port() -> int:
|
|
293
314
|
"""Configure backend port."""
|
|
294
315
|
console.print("\n[bold cyan]🔌 Port Configuration[/bold cyan]")
|
|
@@ -417,6 +438,12 @@ def get_browser_execution_path() -> Optional[str]:
|
|
|
417
438
|
|
|
418
439
|
def main():
|
|
419
440
|
"""Main CLI entry point."""
|
|
441
|
+
# Parse command line arguments
|
|
442
|
+
parser = argparse.ArgumentParser(description="VibeSurf CLI - Browser automation tool")
|
|
443
|
+
parser.add_argument('--no_select_browser', action='store_true',
|
|
444
|
+
help='Skip browser selection and use first available browser (Chrome -> Edge -> Brave)')
|
|
445
|
+
args = parser.parse_args()
|
|
446
|
+
|
|
420
447
|
try:
|
|
421
448
|
# Initialize telemetry
|
|
422
449
|
telemetry = ProductTelemetry()
|
|
@@ -428,6 +455,8 @@ def main():
|
|
|
428
455
|
import vibe_surf
|
|
429
456
|
console.print(f"[dim]Version: {vibe_surf.__version__}[/dim]\n")
|
|
430
457
|
console.print(f"[dim]Author: WarmShao and Community Contributors [/dim]\n")
|
|
458
|
+
console.print("[dim]VibeSurf collects anonymous usage data by default to improve user experience.[/dim]")
|
|
459
|
+
console.print("[dim]To opt out, set environment variable: VIBESURF_ANONYMIZED_TELEMETRY=false[/dim]\n")
|
|
431
460
|
|
|
432
461
|
# Capture telemetry start event
|
|
433
462
|
start_event = CLITelemetryEvent(
|
|
@@ -440,11 +469,30 @@ def main():
|
|
|
440
469
|
# Check for existing browser path from configuration
|
|
441
470
|
browser_path = get_browser_execution_path()
|
|
442
471
|
|
|
443
|
-
# If no valid browser path found,
|
|
472
|
+
# If no valid browser path found, handle based on --no_select_browser flag
|
|
444
473
|
if not browser_path:
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
474
|
+
if args.no_select_browser:
|
|
475
|
+
# Find first available browser without user interaction
|
|
476
|
+
browser_path = find_first_available_browser()
|
|
477
|
+
if not browser_path:
|
|
478
|
+
console.print("[red]❌ No supported browsers found![/red]")
|
|
479
|
+
console.print("[yellow]Please download and install Chrome, Edge, or Brave browser.[/yellow]")
|
|
480
|
+
return
|
|
481
|
+
else:
|
|
482
|
+
# Determine which browser was found
|
|
483
|
+
browser_name = "Unknown"
|
|
484
|
+
if find_chrome_browser() == browser_path:
|
|
485
|
+
browser_name = "Chrome"
|
|
486
|
+
elif find_edge_browser() == browser_path:
|
|
487
|
+
browser_name = "Edge"
|
|
488
|
+
elif find_brave_browser() == browser_path:
|
|
489
|
+
browser_name = "Brave"
|
|
490
|
+
console.print(f"[green]✅ Auto-selected {browser_name}: {browser_path}[/green]")
|
|
491
|
+
else:
|
|
492
|
+
# Interactive browser selection (original behavior)
|
|
493
|
+
browser_path = select_browser()
|
|
494
|
+
if not browser_path:
|
|
495
|
+
return
|
|
448
496
|
|
|
449
497
|
# Port configuration
|
|
450
498
|
port = configure_port()
|
|
@@ -209,14 +209,6 @@ class ChatOpenAICompatible(ChatOpenAI):
|
|
|
209
209
|
|
|
210
210
|
return clean_schema(schema)
|
|
211
211
|
|
|
212
|
-
@overload
|
|
213
|
-
async def ainvoke(self, messages: list[BaseMessage], output_format: None = None) -> ChatInvokeCompletion[str]:
|
|
214
|
-
...
|
|
215
|
-
|
|
216
|
-
@overload
|
|
217
|
-
async def ainvoke(self, messages: list[BaseMessage], output_format: type[T]) -> ChatInvokeCompletion[T]:
|
|
218
|
-
...
|
|
219
|
-
|
|
220
212
|
async def ainvoke(
|
|
221
213
|
self, messages: list[BaseMessage], output_format: type[T] | None = None
|
|
222
214
|
) -> ChatInvokeCompletion[T] | ChatInvokeCompletion[str]:
|
|
@@ -299,7 +291,8 @@ class ChatOpenAICompatible(ChatOpenAI):
|
|
|
299
291
|
|
|
300
292
|
# Add JSON schema to system prompt if requested
|
|
301
293
|
if self.add_schema_to_system_prompt and openai_messages and openai_messages[0]['role'] == 'system':
|
|
302
|
-
schema_text =
|
|
294
|
+
schema_text = "Your response must return JSON with followed format:\n"
|
|
295
|
+
schema_text += f'\n<json_schema>\n{response_format}\n</json_schema>'
|
|
303
296
|
if isinstance(openai_messages[0]['content'], str):
|
|
304
297
|
openai_messages[0]['content'] += schema_text
|
|
305
298
|
elif isinstance(openai_messages[0]['content'], Iterable):
|
vibe_surf/telemetry/views.py
CHANGED
|
@@ -154,3 +154,35 @@ class BackendTelemetryEvent(BaseTelemetryEvent):
|
|
|
154
154
|
error_message: str | None = None
|
|
155
155
|
|
|
156
156
|
name: str = 'backend_event'
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@dataclass
|
|
160
|
+
class VibeSurfAgentParsedOutputEvent(BaseTelemetryEvent):
|
|
161
|
+
"""Telemetry event for VibeSurf Agent parsed output"""
|
|
162
|
+
|
|
163
|
+
version: str
|
|
164
|
+
parsed_output: str | None = None
|
|
165
|
+
action_count: int | None = None
|
|
166
|
+
action_types: list[str] | None = None
|
|
167
|
+
model: str | None = None
|
|
168
|
+
model_provider: str | None = None
|
|
169
|
+
session_id: str | None = None
|
|
170
|
+
thinking: str | None = None
|
|
171
|
+
|
|
172
|
+
name: str = 'vibesurf_agent_parsed_output'
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@dataclass
|
|
176
|
+
class VibeSurfAgentExceptionEvent(BaseTelemetryEvent):
|
|
177
|
+
"""Telemetry event for VibeSurf Agent exceptions"""
|
|
178
|
+
|
|
179
|
+
version: str
|
|
180
|
+
error_message: str
|
|
181
|
+
error_type: str | None = None
|
|
182
|
+
traceback: str | None = None
|
|
183
|
+
model: str | None = None
|
|
184
|
+
model_provider: str | None = None
|
|
185
|
+
session_id: str | None = None
|
|
186
|
+
function_name: str | None = None
|
|
187
|
+
|
|
188
|
+
name: str = 'vibesurf_agent_exception'
|