Skip to content

Commit 8e663df

Browse files
authored
feat(browser): agent core browser use persistent context (#288)
1 parent 94583e5 commit 8e663df

File tree

4 files changed

+62
-36
lines changed

4 files changed

+62
-36
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ dependencies = [
3838
"markdownify>=1.0.0,<2.0.0",
3939
"requests>=2.28.0,<3.0.0",
4040
"aiohttp>=3.8.0,<4.0.0",
41+
"typing_extensions>=4.0.0,<5.0.0",
4142
# Note: Always want the latest tzdata
4243
"tzdata ; platform_system == 'Windows'",
4344
"botocore>=1.39.7,<2.0.0",

src/strands_tools/browser/agent_core_browser.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
from bedrock_agentcore.tools.browser_client import BrowserClient as AgentCoreBrowserClient
1212
from playwright.async_api import Browser as PlaywrightBrowser
13+
from typing_extensions import override
1314

1415
from ..utils.aws_util import resolve_region
1516
from .browser import Browser
@@ -59,6 +60,34 @@ async def create_browser_session(self) -> PlaywrightBrowser:
5960

6061
return browser
6162

63+
@override
64+
async def _setup_session_from_browser(self, browser_or_context):
65+
"""Setup session for AgentCoreBrowser using existing CDP context.
66+
67+
AgentCoreBrowser connects via CDP and uses the default context
68+
that comes with the connection, avoiding the need to create a new one.
69+
70+
Args:
71+
browser_or_context: PlaywrightBrowser from CDP connection
72+
73+
Returns:
74+
Tuple of (browser, context, page)
75+
"""
76+
session_browser = browser_or_context
77+
78+
# CDP connections should have a default context
79+
if not session_browser.contexts:
80+
raise RuntimeError(
81+
"AgentCoreBrowser CDP connection has no contexts. "
82+
"This may indicate a connection issue with the remote browser."
83+
)
84+
85+
# Use the existing default context from CDP connection
86+
session_context = session_browser.contexts[0]
87+
session_page = await session_context.new_page()
88+
89+
return session_browser, session_context, session_page
90+
6291
def close_platform(self) -> None:
6392
for client in self._client_dict.values():
6493
try:

src/strands_tools/browser/browser.py

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,31 @@ async def create_browser_session(self) -> PlaywrightBrowser:
221221
"""
222222
...
223223

224+
async def _setup_session_from_browser(self, browser_or_context):
225+
"""Setup session components from browser or context.
226+
227+
This method can be overridden by subclasses to customize how
228+
browser, context, and page are extracted from the session object.
229+
230+
Args:
231+
browser_or_context: The object returned by create_browser_session()
232+
233+
Returns:
234+
Tuple of (browser, context, page)
235+
"""
236+
if isinstance(browser_or_context, PlaywrightBrowser):
237+
# Normal non-persistent case
238+
session_browser = browser_or_context
239+
session_context = await session_browser.new_context()
240+
session_page = await session_context.new_page()
241+
else:
242+
# Persistent context case
243+
session_context = browser_or_context
244+
session_browser = session_context.browser
245+
session_page = await session_context.new_page()
246+
247+
return session_browser, session_context, session_page
248+
224249
# Session Management Methods
225250
def init_session(self, action: InitSessionAction) -> Dict[str, Any]:
226251
"""Initialize a new browser session."""
@@ -238,19 +263,10 @@ async def _async_init_session(self, action: InitSessionAction) -> Dict[str, Any]
238263

239264
try:
240265
# Create new browser instance for this session
241-
session = await self.create_browser_session()
266+
browser_or_context = await self.create_browser_session()
242267

243-
if isinstance(session, PlaywrightBrowser):
244-
# Normal non-persistent case
245-
session_browser = session
246-
session_context = await session_browser.new_context()
247-
session_page = await session_context.new_page()
248-
249-
else:
250-
# Persistent context case
251-
session_context = session
252-
session_browser = session_context.browser
253-
session_page = await session_context.new_page()
268+
# Let subclasses customize how to extract browser, context, and page
269+
session_browser, session_context, session_page = await self._setup_session_from_browser(browser_or_context)
254270

255271
# Create and store session object
256272
session = BrowserSession(

tests/test_editor.py

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -259,12 +259,7 @@ def test_str_replace_no_backup(self, mock_user_input, temp_file, clean_content_h
259259
"""Test str_replace without creating backup when EDITOR_DISABLE_BACKUP is set."""
260260
mock_user_input.return_value = "y"
261261

262-
result = editor.editor(
263-
command="str_replace",
264-
path=temp_file,
265-
old_str="Line 2",
266-
new_str="Modified Line 2"
267-
)
262+
result = editor.editor(command="str_replace", path=temp_file, old_str="Line 2", new_str="Modified Line 2")
268263

269264
assert result["status"] == "success"
270265

@@ -278,12 +273,7 @@ def test_pattern_replace_no_backup(self, mock_user_input, temp_file, clean_conte
278273
"""Test pattern_replace without creating backup."""
279274
mock_user_input.return_value = "y"
280275

281-
result = editor.editor(
282-
command="pattern_replace",
283-
path=temp_file,
284-
pattern="Line.*",
285-
new_str="Updated Line"
286-
)
276+
result = editor.editor(command="pattern_replace", path=temp_file, pattern="Line.*", new_str="Updated Line")
287277

288278
assert result["status"] == "success"
289279
backup_path = f"{temp_file}.bak"
@@ -295,12 +285,7 @@ def test_insert_no_backup(self, mock_user_input, temp_file, clean_content_histor
295285
"""Test insert without creating backup."""
296286
mock_user_input.return_value = "y"
297287

298-
result = editor.editor(
299-
command="insert",
300-
path=temp_file,
301-
new_str="New line",
302-
insert_line=2
303-
)
288+
result = editor.editor(command="insert", path=temp_file, new_str="New line", insert_line=2)
304289

305290
assert result["status"] == "success"
306291
backup_path = f"{temp_file}.bak"
@@ -315,12 +300,7 @@ def test_backup_created_by_default(self, mock_user_input, temp_file, clean_conte
315300

316301
mock_user_input.return_value = "y"
317302

318-
result = editor.editor(
319-
command="str_replace",
320-
path=temp_file,
321-
old_str="Line 2",
322-
new_str="Modified Line 2"
323-
)
303+
result = editor.editor(command="str_replace", path=temp_file, old_str="Line 2", new_str="Modified Line 2")
324304

325305
assert result["status"] == "success"
326306
backup_path = f"{temp_file}.bak"

0 commit comments

Comments
 (0)