vibesurf 0.1.0__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 vibesurf might be problematic. Click here for more details.
- vibe_surf/__init__.py +12 -0
- vibe_surf/_version.py +34 -0
- vibe_surf/agents/__init__.py +0 -0
- vibe_surf/agents/browser_use_agent.py +1106 -0
- vibe_surf/agents/prompts/__init__.py +1 -0
- vibe_surf/agents/prompts/vibe_surf_prompt.py +176 -0
- vibe_surf/agents/report_writer_agent.py +360 -0
- vibe_surf/agents/vibe_surf_agent.py +1632 -0
- vibe_surf/backend/__init__.py +0 -0
- vibe_surf/backend/api/__init__.py +3 -0
- vibe_surf/backend/api/activity.py +243 -0
- vibe_surf/backend/api/config.py +740 -0
- vibe_surf/backend/api/files.py +322 -0
- vibe_surf/backend/api/models.py +257 -0
- vibe_surf/backend/api/task.py +300 -0
- vibe_surf/backend/database/__init__.py +13 -0
- vibe_surf/backend/database/manager.py +129 -0
- vibe_surf/backend/database/models.py +164 -0
- vibe_surf/backend/database/queries.py +922 -0
- vibe_surf/backend/database/schemas.py +100 -0
- vibe_surf/backend/llm_config.py +182 -0
- vibe_surf/backend/main.py +137 -0
- vibe_surf/backend/migrations/__init__.py +16 -0
- vibe_surf/backend/migrations/init_db.py +303 -0
- vibe_surf/backend/migrations/seed_data.py +236 -0
- vibe_surf/backend/shared_state.py +601 -0
- vibe_surf/backend/utils/__init__.py +7 -0
- vibe_surf/backend/utils/encryption.py +164 -0
- vibe_surf/backend/utils/llm_factory.py +225 -0
- vibe_surf/browser/__init__.py +8 -0
- vibe_surf/browser/agen_browser_profile.py +130 -0
- vibe_surf/browser/agent_browser_session.py +416 -0
- vibe_surf/browser/browser_manager.py +296 -0
- vibe_surf/browser/utils.py +790 -0
- vibe_surf/browser/watchdogs/__init__.py +0 -0
- vibe_surf/browser/watchdogs/action_watchdog.py +291 -0
- vibe_surf/browser/watchdogs/dom_watchdog.py +954 -0
- vibe_surf/chrome_extension/background.js +558 -0
- vibe_surf/chrome_extension/config.js +48 -0
- vibe_surf/chrome_extension/content.js +284 -0
- vibe_surf/chrome_extension/dev-reload.js +47 -0
- vibe_surf/chrome_extension/icons/convert-svg.js +33 -0
- vibe_surf/chrome_extension/icons/logo-preview.html +187 -0
- vibe_surf/chrome_extension/icons/logo.png +0 -0
- vibe_surf/chrome_extension/manifest.json +53 -0
- vibe_surf/chrome_extension/popup.html +134 -0
- vibe_surf/chrome_extension/scripts/api-client.js +473 -0
- vibe_surf/chrome_extension/scripts/main.js +491 -0
- vibe_surf/chrome_extension/scripts/markdown-it.min.js +3 -0
- vibe_surf/chrome_extension/scripts/session-manager.js +599 -0
- vibe_surf/chrome_extension/scripts/ui-manager.js +3687 -0
- vibe_surf/chrome_extension/sidepanel.html +347 -0
- vibe_surf/chrome_extension/styles/animations.css +471 -0
- vibe_surf/chrome_extension/styles/components.css +670 -0
- vibe_surf/chrome_extension/styles/main.css +2307 -0
- vibe_surf/chrome_extension/styles/settings.css +1100 -0
- vibe_surf/cli.py +357 -0
- vibe_surf/controller/__init__.py +0 -0
- vibe_surf/controller/file_system.py +53 -0
- vibe_surf/controller/mcp_client.py +68 -0
- vibe_surf/controller/vibesurf_controller.py +616 -0
- vibe_surf/controller/views.py +37 -0
- vibe_surf/llm/__init__.py +21 -0
- vibe_surf/llm/openai_compatible.py +237 -0
- vibesurf-0.1.0.dist-info/METADATA +97 -0
- vibesurf-0.1.0.dist-info/RECORD +70 -0
- vibesurf-0.1.0.dist-info/WHEEL +5 -0
- vibesurf-0.1.0.dist-info/entry_points.txt +2 -0
- vibesurf-0.1.0.dist-info/licenses/LICENSE +201 -0
- vibesurf-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import List, Optional
|
|
7
|
+
|
|
8
|
+
from browser_use.browser.session import BrowserSession, CDPSession
|
|
9
|
+
from pydantic import Field
|
|
10
|
+
from browser_use.browser.events import (
|
|
11
|
+
NavigationCompleteEvent,
|
|
12
|
+
)
|
|
13
|
+
import time
|
|
14
|
+
from browser_use.browser.profile import BrowserProfile
|
|
15
|
+
from browser_use.browser.views import BrowserStateSummary
|
|
16
|
+
from browser_use.dom.views import TargetInfo
|
|
17
|
+
from vibe_surf.browser.agen_browser_profile import AgentBrowserProfile
|
|
18
|
+
from typing import Self
|
|
19
|
+
|
|
20
|
+
DEFAULT_BROWSER_PROFILE = AgentBrowserProfile()
|
|
21
|
+
|
|
22
|
+
class AgentBrowserSession(BrowserSession):
|
|
23
|
+
"""Isolated browser session for a specific agent."""
|
|
24
|
+
browser_profile: AgentBrowserProfile = Field(
|
|
25
|
+
default_factory=lambda: DEFAULT_BROWSER_PROFILE,
|
|
26
|
+
description='BrowserProfile() options to use for the session, otherwise a default profile will be used',
|
|
27
|
+
)
|
|
28
|
+
main_browser_session: BrowserSession | None = Field(default=None)
|
|
29
|
+
connected_agent: bool = False
|
|
30
|
+
# Add a flag to control DVD animation (for future extensibility)
|
|
31
|
+
disable_dvd_animation: bool = Field(
|
|
32
|
+
default=True,
|
|
33
|
+
description="Disable the DVD screensaver animation on about:blank pages"
|
|
34
|
+
)
|
|
35
|
+
# Custom extensions to load
|
|
36
|
+
custom_extension_paths: List[str] = Field(
|
|
37
|
+
default_factory=list,
|
|
38
|
+
description="List of paths to custom Chrome extensions to load"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
async def connect_agent(self, target_id: str) -> Self:
|
|
42
|
+
"""Register agent to browser with optional target assignment."""
|
|
43
|
+
# First ensure the parent BrowserSession is properly connected
|
|
44
|
+
if not hasattr(self, '_cdp_client_root') or self._cdp_client_root is None:
|
|
45
|
+
await self.connect()
|
|
46
|
+
|
|
47
|
+
assigned_target_ids = self._cdp_session_pool.keys()
|
|
48
|
+
if target_id not in assigned_target_ids:
|
|
49
|
+
self.logger.info(f"Agent {self.id}: Assigned target {target_id}")
|
|
50
|
+
self.agent_focus = await CDPSession.for_target(self._cdp_client_root, target_id, new_socket=True,
|
|
51
|
+
cdp_url=self.cdp_url)
|
|
52
|
+
await self.agent_focus.cdp_client.send.Target.activateTarget(
|
|
53
|
+
params={'targetId': target_id})
|
|
54
|
+
await self.agent_focus.cdp_client.send.Runtime.runIfWaitingForDebugger(
|
|
55
|
+
session_id=self.agent_focus.session_id)
|
|
56
|
+
self._cdp_session_pool[target_id] = self.agent_focus
|
|
57
|
+
self.connected_agent = True
|
|
58
|
+
return self
|
|
59
|
+
|
|
60
|
+
async def disconnect_agent(self) -> None:
|
|
61
|
+
"""Disconnect all agent-specific CDP sessions and cleanup security context."""
|
|
62
|
+
for session in self._cdp_session_pool.values():
|
|
63
|
+
await session.disconnect()
|
|
64
|
+
self._cdp_session_pool.clear()
|
|
65
|
+
self.connected_agent = False
|
|
66
|
+
|
|
67
|
+
async def _cdp_get_all_pages(
|
|
68
|
+
self,
|
|
69
|
+
include_http: bool = True,
|
|
70
|
+
include_about: bool = True,
|
|
71
|
+
include_pages: bool = True,
|
|
72
|
+
include_iframes: bool = False,
|
|
73
|
+
include_workers: bool = False,
|
|
74
|
+
include_chrome: bool = False,
|
|
75
|
+
include_chrome_extensions: bool = False,
|
|
76
|
+
include_chrome_error: bool = False,
|
|
77
|
+
) -> list[TargetInfo]:
|
|
78
|
+
"""Get all browser pages/tabs using CDP Target.getTargets."""
|
|
79
|
+
# Safety check - return empty list if browser not connected yet
|
|
80
|
+
if not self._cdp_client_root:
|
|
81
|
+
return []
|
|
82
|
+
targets = await self.cdp_client.send.Target.getTargets()
|
|
83
|
+
if self.connected_agent:
|
|
84
|
+
assigned_target_ids = self._cdp_session_pool.keys()
|
|
85
|
+
return [
|
|
86
|
+
t
|
|
87
|
+
for t in targets.get('targetInfos', [])
|
|
88
|
+
if self._is_valid_target(
|
|
89
|
+
t,
|
|
90
|
+
include_http=include_http,
|
|
91
|
+
include_about=include_about,
|
|
92
|
+
include_pages=include_pages,
|
|
93
|
+
include_iframes=include_iframes,
|
|
94
|
+
include_workers=include_workers,
|
|
95
|
+
include_chrome=include_chrome,
|
|
96
|
+
include_chrome_extensions=include_chrome_extensions,
|
|
97
|
+
include_chrome_error=include_chrome_error,
|
|
98
|
+
) and t.get('targetId') in assigned_target_ids
|
|
99
|
+
]
|
|
100
|
+
else:
|
|
101
|
+
# Filter for valid page/tab targets only
|
|
102
|
+
return [
|
|
103
|
+
t
|
|
104
|
+
for t in targets.get('targetInfos', [])
|
|
105
|
+
if self._is_valid_target(
|
|
106
|
+
t,
|
|
107
|
+
include_http=include_http,
|
|
108
|
+
include_about=include_about,
|
|
109
|
+
include_pages=include_pages,
|
|
110
|
+
include_iframes=include_iframes,
|
|
111
|
+
include_workers=include_workers,
|
|
112
|
+
include_chrome=include_chrome,
|
|
113
|
+
include_chrome_extensions=include_chrome_extensions,
|
|
114
|
+
include_chrome_error=include_chrome_error,
|
|
115
|
+
)
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
async def attach_all_watchdogs(self) -> None:
|
|
119
|
+
"""Initialize and attach all watchdogs EXCEPT AboutBlankWatchdog to disable DVD animation."""
|
|
120
|
+
# Prevent duplicate watchdog attachment
|
|
121
|
+
if hasattr(self, '_watchdogs_attached') and self._watchdogs_attached:
|
|
122
|
+
self.logger.debug('Watchdogs already attached, skipping duplicate attachment')
|
|
123
|
+
return
|
|
124
|
+
|
|
125
|
+
# Import all watchdogs except AboutBlankWatchdog
|
|
126
|
+
from vibe_surf.browser.watchdogs.action_watchdog import CustomActionWatchdog
|
|
127
|
+
from vibe_surf.browser.watchdogs.dom_watchdog import CustomDOMWatchdog
|
|
128
|
+
|
|
129
|
+
from browser_use.browser.downloads_watchdog import DownloadsWatchdog
|
|
130
|
+
from browser_use.browser.local_browser_watchdog import LocalBrowserWatchdog
|
|
131
|
+
from browser_use.browser.permissions_watchdog import PermissionsWatchdog
|
|
132
|
+
from browser_use.browser.popups_watchdog import PopupsWatchdog
|
|
133
|
+
from browser_use.browser.screenshot_watchdog import ScreenshotWatchdog
|
|
134
|
+
from browser_use.browser.security_watchdog import SecurityWatchdog
|
|
135
|
+
|
|
136
|
+
# NOTE: AboutBlankWatchdog is deliberately excluded to disable DVD animation
|
|
137
|
+
|
|
138
|
+
self.logger.info('🚫 VibeSurfBrowserSession: AboutBlankWatchdog disabled - no DVD animation will be shown')
|
|
139
|
+
|
|
140
|
+
# Initialize DownloadsWatchdog
|
|
141
|
+
DownloadsWatchdog.model_rebuild()
|
|
142
|
+
self._downloads_watchdog = DownloadsWatchdog(event_bus=self.event_bus, browser_session=self)
|
|
143
|
+
self._downloads_watchdog.attach_to_session()
|
|
144
|
+
if self.browser_profile.auto_download_pdfs:
|
|
145
|
+
self.logger.info('📄 PDF auto-download enabled for this session')
|
|
146
|
+
|
|
147
|
+
# Initialize LocalBrowserWatchdog
|
|
148
|
+
LocalBrowserWatchdog.model_rebuild()
|
|
149
|
+
self._local_browser_watchdog = LocalBrowserWatchdog(event_bus=self.event_bus, browser_session=self)
|
|
150
|
+
self._local_browser_watchdog.attach_to_session()
|
|
151
|
+
|
|
152
|
+
# Initialize SecurityWatchdog (hooks NavigationWatchdog and implements allowed_domains restriction)
|
|
153
|
+
SecurityWatchdog.model_rebuild()
|
|
154
|
+
self._security_watchdog = SecurityWatchdog(event_bus=self.event_bus, browser_session=self)
|
|
155
|
+
self._security_watchdog.attach_to_session()
|
|
156
|
+
|
|
157
|
+
# Initialize PopupsWatchdog (handles accepting and dismissing JS dialogs, alerts, confirm, onbeforeunload, etc.)
|
|
158
|
+
PopupsWatchdog.model_rebuild()
|
|
159
|
+
self._popups_watchdog = PopupsWatchdog(event_bus=self.event_bus, browser_session=self)
|
|
160
|
+
self._popups_watchdog.attach_to_session()
|
|
161
|
+
|
|
162
|
+
# Initialize PermissionsWatchdog (handles granting and revoking browser permissions like clipboard, microphone, camera, etc.)
|
|
163
|
+
PermissionsWatchdog.model_rebuild()
|
|
164
|
+
self._permissions_watchdog = PermissionsWatchdog(event_bus=self.event_bus, browser_session=self)
|
|
165
|
+
self._permissions_watchdog.attach_to_session()
|
|
166
|
+
|
|
167
|
+
# Initialize DefaultActionWatchdog (handles all default actions like click, type, scroll, go back, go forward, refresh, wait, send keys, upload file, scroll to text, etc.)
|
|
168
|
+
CustomActionWatchdog.model_rebuild()
|
|
169
|
+
self._default_action_watchdog = CustomActionWatchdog(event_bus=self.event_bus, browser_session=self)
|
|
170
|
+
self._default_action_watchdog.attach_to_session()
|
|
171
|
+
|
|
172
|
+
# Initialize ScreenshotWatchdog (handles taking screenshots of the browser)
|
|
173
|
+
ScreenshotWatchdog.model_rebuild()
|
|
174
|
+
self._screenshot_watchdog = ScreenshotWatchdog(event_bus=self.event_bus, browser_session=self)
|
|
175
|
+
self._screenshot_watchdog.attach_to_session()
|
|
176
|
+
|
|
177
|
+
# Initialize DOMWatchdog (handles building the DOM tree and detecting interactive elements, depends on ScreenshotWatchdog)
|
|
178
|
+
CustomDOMWatchdog.model_rebuild()
|
|
179
|
+
self._dom_watchdog = CustomDOMWatchdog(event_bus=self.event_bus, browser_session=self)
|
|
180
|
+
self._dom_watchdog.attach_to_session()
|
|
181
|
+
|
|
182
|
+
# Mark watchdogs as attached to prevent duplicate attachment
|
|
183
|
+
self._watchdogs_attached = True
|
|
184
|
+
|
|
185
|
+
self.logger.info('✅ VibeSurfBrowserSession: All watchdogs attached (AboutBlankWatchdog excluded)')
|
|
186
|
+
|
|
187
|
+
async def _ensure_minimal_about_blank_tab(self) -> None:
|
|
188
|
+
"""
|
|
189
|
+
Ensure there's at least one about:blank tab without any animation.
|
|
190
|
+
This replaces AboutBlankWatchdog's functionality but without the DVD animation.
|
|
191
|
+
"""
|
|
192
|
+
try:
|
|
193
|
+
# Get all page targets using CDP
|
|
194
|
+
page_targets = await self._cdp_get_all_pages()
|
|
195
|
+
|
|
196
|
+
# If no tabs exist at all, create one to keep browser alive
|
|
197
|
+
if len(page_targets) == 0:
|
|
198
|
+
self.logger.info('[VibeSurfBrowserSession] No tabs exist, creating new about:blank tab (no animation)')
|
|
199
|
+
from browser_use.browser.events import NavigateToUrlEvent
|
|
200
|
+
navigate_event = self.event_bus.dispatch(NavigateToUrlEvent(url='about:blank', new_tab=True))
|
|
201
|
+
await navigate_event
|
|
202
|
+
# Note: NO DVD screensaver injection here!
|
|
203
|
+
|
|
204
|
+
except Exception as e:
|
|
205
|
+
self.logger.error(f'[VibeSurfBrowserSession] Error ensuring about:blank tab: {e}')
|
|
206
|
+
|
|
207
|
+
async def on_BrowserStartEvent(self, event) -> dict[str, str]:
|
|
208
|
+
"""Override to ensure minimal about:blank handling without animation."""
|
|
209
|
+
# Call parent implementation first
|
|
210
|
+
result = await super().on_BrowserStartEvent(event)
|
|
211
|
+
|
|
212
|
+
# Ensure we have at least one tab without animation
|
|
213
|
+
await self._ensure_minimal_about_blank_tab()
|
|
214
|
+
|
|
215
|
+
return result
|
|
216
|
+
|
|
217
|
+
def get_cdp_session_pool(self):
|
|
218
|
+
return self._cdp_session_pool
|
|
219
|
+
|
|
220
|
+
async def active_focus_page(self):
|
|
221
|
+
if self.agent_focus is None:
|
|
222
|
+
self.logger.info('No active focus page found, cannot active!')
|
|
223
|
+
return
|
|
224
|
+
await self.get_or_create_cdp_session(self.agent_focus.target_id, focus=True)
|
|
225
|
+
|
|
226
|
+
async def navigate_to_url(self, url: str, new_tab: bool = False) -> None:
|
|
227
|
+
"""
|
|
228
|
+
Concurrent navigation method that bypasses serial bottlenecks in on_NavigateToUrlEvent.
|
|
229
|
+
|
|
230
|
+
This method performs minimal event dispatching and direct CDP calls for maximum concurrency.
|
|
231
|
+
"""
|
|
232
|
+
if not self.agent_focus:
|
|
233
|
+
self.logger.warning('Cannot navigate - browser not connected')
|
|
234
|
+
return
|
|
235
|
+
|
|
236
|
+
target_id = None
|
|
237
|
+
|
|
238
|
+
try:
|
|
239
|
+
# Minimal target handling - avoid expensive _cdp_get_all_pages() call
|
|
240
|
+
if new_tab:
|
|
241
|
+
# Create new tab directly via CDP - no event system overhead
|
|
242
|
+
result = await self._cdp_client_root.send.Target.createTarget(
|
|
243
|
+
params={'url': 'about:blank', 'newWindow': False, 'background': False}
|
|
244
|
+
)
|
|
245
|
+
target_id = result['targetId']
|
|
246
|
+
|
|
247
|
+
# Create CDP session with dedicated WebSocket for this target
|
|
248
|
+
session = await self.get_or_create_cdp_session(target_id, focus=True)
|
|
249
|
+
self.agent_focus = session
|
|
250
|
+
|
|
251
|
+
# Activate target without events
|
|
252
|
+
await session.cdp_client.send.Target.activateTarget(params={'targetId': target_id})
|
|
253
|
+
await session.cdp_client.send.Runtime.runIfWaitingForDebugger(session_id=session.session_id)
|
|
254
|
+
else:
|
|
255
|
+
# Use current tab - no tab switching events
|
|
256
|
+
target_id = self.agent_focus.target_id
|
|
257
|
+
|
|
258
|
+
# Direct CDP navigation - bypasses all event system overhead
|
|
259
|
+
await self.agent_focus.cdp_client.send.Page.navigate(
|
|
260
|
+
params={
|
|
261
|
+
'url': url,
|
|
262
|
+
'transitionType': 'address_bar',
|
|
263
|
+
},
|
|
264
|
+
session_id=self.agent_focus.session_id,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
# Minimal delay for navigation to start
|
|
268
|
+
await asyncio.sleep(0.2)
|
|
269
|
+
|
|
270
|
+
# Optional: Dispatch only essential completion event (non-blocking)
|
|
271
|
+
self.event_bus.dispatch(
|
|
272
|
+
NavigationCompleteEvent(
|
|
273
|
+
target_id=target_id,
|
|
274
|
+
url=url,
|
|
275
|
+
status=None,
|
|
276
|
+
)
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
except Exception as e:
|
|
280
|
+
self.logger.error(f'Concurrent navigation failed: {type(e).__name__}: {e}')
|
|
281
|
+
if target_id:
|
|
282
|
+
# Non-blocking error event
|
|
283
|
+
self.event_bus.dispatch(
|
|
284
|
+
NavigationCompleteEvent(
|
|
285
|
+
target_id=target_id,
|
|
286
|
+
url=url,
|
|
287
|
+
error_message=f'{type(e).__name__}: {e}',
|
|
288
|
+
)
|
|
289
|
+
)
|
|
290
|
+
raise
|
|
291
|
+
|
|
292
|
+
async def _wait_for_stable_network(self):
|
|
293
|
+
"""Wait for page stability - simplified for CDP-only branch."""
|
|
294
|
+
start_time = time.time()
|
|
295
|
+
page_url = await self.get_current_page_url()
|
|
296
|
+
not_a_meaningful_website = page_url.lower().split(':', 1)[0] not in ('http', 'https')
|
|
297
|
+
|
|
298
|
+
# Wait for page stability using browser profile settings (main branch pattern)
|
|
299
|
+
if not not_a_meaningful_website:
|
|
300
|
+
self.logger.debug('🔍 DOMWatchdog.on_BrowserStateRequestEvent: ⏳ Waiting for page stability...')
|
|
301
|
+
try:
|
|
302
|
+
# Apply minimum wait time first (let page settle)
|
|
303
|
+
min_wait = self.browser_profile.minimum_wait_page_load_time
|
|
304
|
+
if min_wait > 0:
|
|
305
|
+
self.logger.debug(f'⏳ Minimum wait: {min_wait}s')
|
|
306
|
+
await asyncio.sleep(min_wait)
|
|
307
|
+
|
|
308
|
+
# Apply network idle wait time (for dynamic content like iframes)
|
|
309
|
+
network_idle_wait = self.browser_profile.wait_for_network_idle_page_load_time
|
|
310
|
+
if network_idle_wait > 0:
|
|
311
|
+
self.logger.debug(f'⏳ Network idle wait: {network_idle_wait}s')
|
|
312
|
+
await asyncio.sleep(network_idle_wait)
|
|
313
|
+
|
|
314
|
+
elapsed = time.time() - start_time
|
|
315
|
+
self.logger.debug(f'✅ Page stability wait completed in {elapsed:.2f}s')
|
|
316
|
+
except Exception as e:
|
|
317
|
+
self.logger.warning(
|
|
318
|
+
f'🔍 DOMWatchdog.on_BrowserStateRequestEvent: Network waiting failed: {e}, continuing anyway...'
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
async def take_screenshot(self, target_id: Optional[str] = None, format: str = 'png') -> str:
|
|
322
|
+
"""
|
|
323
|
+
Concurrent screenshot method that bypasses serial bottlenecks in ScreenshotWatchdog.
|
|
324
|
+
|
|
325
|
+
This method performs direct CDP calls for maximum concurrency.
|
|
326
|
+
"""
|
|
327
|
+
if target_id is None:
|
|
328
|
+
if not self.agent_focus:
|
|
329
|
+
self.logger.warning('No page focus to get html, please specify a target id.')
|
|
330
|
+
return ''
|
|
331
|
+
target_id = self.agent_focus.target_id
|
|
332
|
+
cdp_session = await self.get_or_create_cdp_session(target_id, focus=False)
|
|
333
|
+
await self._wait_for_stable_network()
|
|
334
|
+
|
|
335
|
+
try:
|
|
336
|
+
ready_state = await cdp_session.cdp_client.send.Runtime.evaluate(
|
|
337
|
+
params={'expression': 'document.readyState'}, session_id=cdp_session.session_id
|
|
338
|
+
)
|
|
339
|
+
except Exception:
|
|
340
|
+
pass
|
|
341
|
+
|
|
342
|
+
try:
|
|
343
|
+
from cdp_use.cdp.page import CaptureScreenshotParameters
|
|
344
|
+
# Direct CDP screenshot - bypasses all event system overhead
|
|
345
|
+
params = CaptureScreenshotParameters(format=format, captureBeyondViewport=False, quality=90)
|
|
346
|
+
result = await cdp_session.cdp_client.send.Page.captureScreenshot(
|
|
347
|
+
params=params,
|
|
348
|
+
session_id=cdp_session.session_id,
|
|
349
|
+
)
|
|
350
|
+
return result['data']
|
|
351
|
+
|
|
352
|
+
except Exception as e:
|
|
353
|
+
self.logger.error(f'Concurrent screenshot failed: {type(e).__name__}: {e}')
|
|
354
|
+
raise
|
|
355
|
+
|
|
356
|
+
async def get_html_content(self, target_id: Optional[str] = None) -> str:
|
|
357
|
+
"""
|
|
358
|
+
Get html content of current page
|
|
359
|
+
:return:
|
|
360
|
+
"""
|
|
361
|
+
if target_id is None:
|
|
362
|
+
if not self.agent_focus:
|
|
363
|
+
self.logger.warning('No page focus to get html, please specify a target id.')
|
|
364
|
+
return ''
|
|
365
|
+
target_id = self.agent_focus.target_id
|
|
366
|
+
cdp_session = await self.get_or_create_cdp_session(target_id, focus=True)
|
|
367
|
+
await self._wait_for_stable_network()
|
|
368
|
+
|
|
369
|
+
try:
|
|
370
|
+
ready_state = await cdp_session.cdp_client.send.Runtime.evaluate(
|
|
371
|
+
params={'expression': 'document.readyState'}, session_id=cdp_session.session_id
|
|
372
|
+
)
|
|
373
|
+
except Exception:
|
|
374
|
+
await self._wait_for_stable_network()
|
|
375
|
+
|
|
376
|
+
try:
|
|
377
|
+
# Get the HTML content
|
|
378
|
+
body_id = await cdp_session.cdp_client.send.DOM.getDocument(session_id=cdp_session.session_id)
|
|
379
|
+
page_html_result = await cdp_session.cdp_client.send.DOM.getOuterHTML(
|
|
380
|
+
params={'backendNodeId': body_id['root']['backendNodeId']}, session_id=cdp_session.session_id
|
|
381
|
+
)
|
|
382
|
+
except Exception as e:
|
|
383
|
+
raise RuntimeError(f"Couldn't extract page content: {e}")
|
|
384
|
+
|
|
385
|
+
page_html = page_html_result['outerHTML']
|
|
386
|
+
return page_html
|
|
387
|
+
|
|
388
|
+
async def get_browser_state_summary(
|
|
389
|
+
self,
|
|
390
|
+
cache_clickable_elements_hashes: bool = True,
|
|
391
|
+
include_screenshot: bool = True,
|
|
392
|
+
cached: bool = False,
|
|
393
|
+
include_recent_events: bool = False,
|
|
394
|
+
) -> BrowserStateSummary:
|
|
395
|
+
if cached and self._cached_browser_state_summary is not None and self._cached_browser_state_summary.dom_state:
|
|
396
|
+
# Don't use cached state if it has 0 interactive elements
|
|
397
|
+
selector_map = self._cached_browser_state_summary.dom_state.selector_map
|
|
398
|
+
|
|
399
|
+
# Don't use cached state if we need a screenshot but the cached state doesn't have one
|
|
400
|
+
if include_screenshot and not self._cached_browser_state_summary.screenshot:
|
|
401
|
+
self.logger.debug('⚠️ Cached browser state has no screenshot, fetching fresh state with screenshot')
|
|
402
|
+
# Fall through to fetch fresh state with screenshot
|
|
403
|
+
elif selector_map and len(selector_map) > 0:
|
|
404
|
+
self.logger.debug('🔄 Using pre-cached browser state summary for open tab')
|
|
405
|
+
return self._cached_browser_state_summary
|
|
406
|
+
else:
|
|
407
|
+
self.logger.debug('⚠️ Cached browser state has 0 interactive elements, fetching fresh state')
|
|
408
|
+
# Fall through to fetch fresh state
|
|
409
|
+
|
|
410
|
+
browser_state = await self._dom_watchdog.get_browser_state_no_event_bus(
|
|
411
|
+
include_dom=True,
|
|
412
|
+
include_screenshot=include_screenshot,
|
|
413
|
+
cache_clickable_elements_hashes=cache_clickable_elements_hashes,
|
|
414
|
+
include_recent_events=include_recent_events
|
|
415
|
+
)
|
|
416
|
+
return browser_state
|