Skip to content

Commit 1ba72f8

Browse files
committed
feat: add SessionConfiguration with proxy, extensions, and profile support
Replace ad-hoc TypedDicts in browser_client.py with composable dataclasses in config.py following the established BrowserConfiguration pattern: - ProxyCredentials, ExternalProxy, ProxyConfiguration for proxy routing - ExtensionS3Location, BrowserExtension for loading browser extensions - SessionConfiguration composite that produces kwargs for start() - Add extensions and profile_configuration params to start() and browser_session() Usage: client.start(**session_config.to_dict())
1 parent a637826 commit 1ba72f8

8 files changed

Lines changed: 1557 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## [Unreleased]
4+
5+
### Added
6+
- feat: add SessionConfiguration with proxy, extensions, and profile support for browser sessions (#274)
7+
38
## [1.3.2] - 2026-02-23
49

510
### Added

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ classifiers = [
2626
"Topic :: Software Development :: Libraries :: Python Modules",
2727
]
2828
dependencies = [
29-
"boto3>=1.40.52",
30-
"botocore>=1.40.52",
29+
"boto3>=1.42.54",
30+
"botocore>=1.42.54",
3131
"pydantic>=2.0.0,<2.41.3",
3232
"urllib3>=1.26.0",
3333
"starlette>=0.46.2",

src/bedrock_agentcore/tools/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,42 @@
33
from .browser_client import BrowserClient, browser_session
44
from .code_interpreter_client import CodeInterpreter, code_session
55
from .config import (
6+
BasicAuth,
67
BrowserConfiguration,
8+
BrowserExtension,
79
BrowserSigningConfiguration,
810
CodeInterpreterConfiguration,
11+
ExtensionS3Location,
12+
ExternalProxy,
913
NetworkConfiguration,
14+
ProfileConfiguration,
15+
ProxyConfiguration,
16+
ProxyCredentials,
1017
RecordingConfiguration,
18+
SessionConfiguration,
1119
ViewportConfiguration,
1220
VpcConfig,
1321
create_browser_config,
1422
)
1523

1624
__all__ = [
25+
"BasicAuth",
1726
"BrowserClient",
1827
"browser_session",
1928
"CodeInterpreter",
2029
"code_session",
2130
"BrowserConfiguration",
31+
"BrowserExtension",
2232
"BrowserSigningConfiguration",
2333
"CodeInterpreterConfiguration",
34+
"ExtensionS3Location",
35+
"ExternalProxy",
2436
"NetworkConfiguration",
37+
"ProfileConfiguration",
38+
"ProxyConfiguration",
39+
"ProxyCredentials",
2540
"RecordingConfiguration",
41+
"SessionConfiguration",
2642
"ViewportConfiguration",
2743
"VpcConfig",
2844
"create_browser_config",

src/bedrock_agentcore/tools/browser_client.py

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import secrets
1212
import uuid
1313
from contextlib import contextmanager
14-
from typing import Dict, Generator, Optional, Tuple
14+
from typing import Any, Dict, Generator, List, Optional, Tuple, Union
1515
from urllib.parse import urlparse
1616

1717
import boto3
@@ -22,6 +22,13 @@
2222
from bedrock_agentcore._utils.user_agent import build_user_agent_suffix
2323

2424
from .._utils.endpoints import get_control_plane_endpoint, get_data_plane_endpoint
25+
from .config import BrowserExtension, ProfileConfiguration, ProxyConfiguration, ViewportConfiguration
26+
27+
28+
def _to_dict(value):
29+
"""Convert a dataclass or dict to a dict. Passes dicts through unchanged."""
30+
return value.to_dict() if hasattr(value, "to_dict") else value
31+
2532

2633
DEFAULT_IDENTIFIER = "aws.browser.v1"
2734
DEFAULT_SESSION_TIMEOUT = 3600
@@ -288,7 +295,10 @@ def start(
288295
identifier: Optional[str] = DEFAULT_IDENTIFIER,
289296
name: Optional[str] = None,
290297
session_timeout_seconds: Optional[int] = DEFAULT_SESSION_TIMEOUT,
291-
viewport: Optional[Dict[str, int]] = None,
298+
viewport: Optional[Union[ViewportConfiguration, Dict[str, int]]] = None,
299+
proxy_configuration: Optional[Union[ProxyConfiguration, Dict[str, Any]]] = None,
300+
extensions: Optional[List[Union[BrowserExtension, Dict[str, Any]]]] = None,
301+
profile_configuration: Optional[Union[ProfileConfiguration, Dict[str, Any]]] = None,
292302
) -> str:
293303
"""Start a browser sandbox session.
294304
@@ -300,8 +310,20 @@ def start(
300310
name (Optional[str]): A name for this session.
301311
session_timeout_seconds (Optional[int]): The timeout for the session in seconds.
302312
Range: 1-28800 (8 hours). Default: 3600 (1 hour).
303-
viewport (Optional[Dict[str, int]]): The viewport dimensions:
313+
viewport (Optional[Union[ViewportConfiguration, Dict[str, int]]]): The viewport
314+
dimensions. Can be a ViewportConfiguration dataclass or a plain dict:
304315
{'width': 1920, 'height': 1080}
316+
proxy_configuration (Optional[Union[ProxyConfiguration, Dict[str, Any]]]): Proxy
317+
configuration for routing browser traffic through external proxy servers.
318+
Can be a ProxyConfiguration dataclass or a plain dict matching the API shape.
319+
extensions (Optional[List[Union[BrowserExtension, Dict[str, Any]]]]): List of
320+
browser extensions to load into the session. Each element can be a
321+
BrowserExtension dataclass or a plain dict:
322+
[{"location": {"s3": {"bucket": "...", "prefix": "..."}}}]
323+
profile_configuration (Optional[Union[ProfileConfiguration, Dict[str, Any]]]): Profile
324+
configuration for persisting browser state across sessions. Can be a
325+
ProfileConfiguration dataclass or a plain dict:
326+
{"profileIdentifier": "my-profile-id"}
305327
306328
Returns:
307329
str: The session ID of the newly created session.
@@ -316,6 +338,20 @@ def start(
316338
... viewport={'width': 1920, 'height': 1080},
317339
... session_timeout_seconds=7200 # 2 hours
318340
... )
341+
>>>
342+
>>> # Use proxy configuration
343+
>>> session_id = client.start(
344+
... proxy_configuration={
345+
... "proxies": [{
346+
... "externalProxy": {
347+
... "server": "proxy.example.com",
348+
... "port": 8080,
349+
... "domainPatterns": [".example.com"],
350+
... }
351+
... }],
352+
... "bypass": {"domainPatterns": [".amazonaws.com"]}
353+
... }
354+
... )
319355
"""
320356
self.logger.info("Starting browser session...")
321357

@@ -326,7 +362,16 @@ def start(
326362
}
327363

328364
if viewport is not None:
329-
request_params["viewPort"] = viewport
365+
request_params["viewPort"] = _to_dict(viewport)
366+
367+
if proxy_configuration is not None:
368+
request_params["proxyConfiguration"] = _to_dict(proxy_configuration)
369+
370+
if extensions is not None:
371+
request_params["extensions"] = [_to_dict(e) for e in extensions]
372+
373+
if profile_configuration is not None:
374+
request_params["profileConfiguration"] = _to_dict(profile_configuration)
330375

331376
response = self.data_plane_client.start_browser_session(**request_params)
332377

@@ -581,14 +626,26 @@ def release_control(self):
581626

582627
@contextmanager
583628
def browser_session(
584-
region: str, viewport: Optional[Dict[str, int]] = None, identifier: Optional[str] = None
629+
region: str,
630+
viewport: Optional[Union[ViewportConfiguration, Dict[str, int]]] = None,
631+
identifier: Optional[str] = None,
632+
proxy_configuration: Optional[Union[ProxyConfiguration, Dict[str, Any]]] = None,
633+
extensions: Optional[List[Union[BrowserExtension, Dict[str, Any]]]] = None,
634+
profile_configuration: Optional[Union[ProfileConfiguration, Dict[str, Any]]] = None,
585635
) -> Generator[BrowserClient, None, None]:
586636
"""Context manager for creating and managing a browser sandbox session.
587637
588638
Args:
589639
region (str): AWS region.
590-
viewport (Optional[Dict[str, int]]): Viewport dimensions.
640+
viewport (Optional[Union[ViewportConfiguration, Dict[str, int]]]): Viewport dimensions.
641+
Can be a ViewportConfiguration dataclass or a plain dict.
591642
identifier (Optional[str]): Browser identifier (system or custom).
643+
proxy_configuration (Optional[Union[ProxyConfiguration, Dict[str, Any]]]): Proxy
644+
configuration. Can be a ProxyConfiguration dataclass or a plain dict.
645+
extensions (Optional[List[Union[BrowserExtension, Dict[str, Any]]]]): Browser
646+
extensions. Each element can be a BrowserExtension dataclass or a plain dict.
647+
profile_configuration (Optional[Union[ProfileConfiguration, Dict[str, Any]]]): Profile
648+
configuration. Can be a ProfileConfiguration dataclass or a plain dict.
592649
593650
Yields:
594651
BrowserClient: An initialized and started browser client.
@@ -602,13 +659,26 @@ def browser_session(
602659
>>> with browser_session('us-west-2', identifier='my-signed-browser') as client:
603660
... # Automation with reduced CAPTCHA friction
604661
... pass
662+
...
663+
>>> # Use proxy configuration
664+
>>> with browser_session('us-west-2', proxy_configuration={
665+
... "proxies": [{"externalProxy": {"server": "proxy.corp.com", "port": 8080}}],
666+
... "bypass": {"domainPatterns": [".amazonaws.com"]}
667+
... }) as client:
668+
... ws_url, headers = client.generate_ws_headers()
605669
"""
606670
client = BrowserClient(region)
607671
start_kwargs = {}
608672
if viewport is not None:
609673
start_kwargs["viewport"] = viewport
610674
if identifier is not None:
611675
start_kwargs["identifier"] = identifier
676+
if proxy_configuration is not None:
677+
start_kwargs["proxy_configuration"] = proxy_configuration
678+
if extensions is not None:
679+
start_kwargs["extensions"] = extensions
680+
if profile_configuration is not None:
681+
start_kwargs["profile_configuration"] = profile_configuration
612682

613683
client.start(**start_kwargs)
614684

src/bedrock_agentcore/tools/config.py

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,174 @@ def mobile(cls) -> "ViewportConfiguration":
198198
return cls(width=375, height=667)
199199

200200

201+
@dataclass
202+
class BasicAuth:
203+
"""HTTP Basic Auth credentials stored in Secrets Manager.
204+
205+
Attributes:
206+
secret_arn: ARN of the Secrets Manager secret containing
207+
{"username": "...", "password": "..."} JSON
208+
"""
209+
210+
secret_arn: str
211+
212+
def to_dict(self) -> Dict:
213+
"""Convert to API-compatible dictionary."""
214+
return {"secretArn": self.secret_arn}
215+
216+
217+
@dataclass
218+
class ProxyCredentials:
219+
"""Credentials for authenticating with a proxy server.
220+
221+
Currently supports HTTP Basic Auth. Modeled as a union to allow
222+
future credential types (bearer token, mTLS, etc.) without breaking changes.
223+
224+
Attributes:
225+
basic_auth: HTTP Basic Auth credentials via Secrets Manager
226+
"""
227+
228+
basic_auth: Optional[BasicAuth] = None
229+
230+
def to_dict(self) -> Dict:
231+
"""Convert to API-compatible dictionary."""
232+
creds = {}
233+
if self.basic_auth:
234+
creds["basicAuth"] = self.basic_auth.to_dict()
235+
return creds
236+
237+
238+
@dataclass
239+
class ExternalProxy:
240+
"""Configuration for an external proxy server.
241+
242+
Attributes:
243+
server: Proxy server hostname
244+
port: Proxy server port
245+
domain_patterns: Domain patterns to route through this proxy
246+
credentials: Optional credentials for proxy authentication
247+
"""
248+
249+
server: str
250+
port: int
251+
domain_patterns: Optional[List[str]] = None
252+
credentials: Optional[ProxyCredentials] = None
253+
254+
def to_dict(self) -> Dict:
255+
"""Convert to API-compatible dictionary."""
256+
proxy = {"server": self.server, "port": self.port}
257+
if self.domain_patterns:
258+
proxy["domainPatterns"] = self.domain_patterns
259+
if self.credentials:
260+
proxy["credentials"] = self.credentials.to_dict()
261+
return {"externalProxy": proxy}
262+
263+
264+
@dataclass
265+
class ProxyConfiguration:
266+
"""Proxy configuration for routing browser traffic through external proxy servers.
267+
268+
Attributes:
269+
proxies: List of external proxy configurations
270+
bypass_patterns: Domain patterns that bypass all proxies
271+
"""
272+
273+
proxies: List[ExternalProxy]
274+
bypass_patterns: Optional[List[str]] = None
275+
276+
def to_dict(self) -> Dict:
277+
"""Convert to API-compatible dictionary."""
278+
config = {"proxies": [p.to_dict() for p in self.proxies]}
279+
if self.bypass_patterns:
280+
config["bypass"] = {"domainPatterns": self.bypass_patterns}
281+
return config
282+
283+
284+
@dataclass
285+
class ExtensionS3Location:
286+
"""S3 location for a browser extension.
287+
288+
Attributes:
289+
bucket: S3 bucket name
290+
prefix: S3 key prefix for the extension
291+
version_id: Optional S3 object version ID
292+
"""
293+
294+
bucket: str
295+
prefix: str
296+
version_id: Optional[str] = None
297+
298+
def to_dict(self) -> Dict:
299+
"""Convert to API-compatible dictionary."""
300+
location = {"bucket": self.bucket, "prefix": self.prefix}
301+
if self.version_id:
302+
location["versionId"] = self.version_id
303+
return location
304+
305+
306+
@dataclass
307+
class BrowserExtension:
308+
"""A browser extension to load into a session.
309+
310+
Attributes:
311+
s3_location: S3 location of the extension package
312+
"""
313+
314+
s3_location: ExtensionS3Location
315+
316+
def to_dict(self) -> Dict:
317+
"""Convert to API-compatible dictionary."""
318+
return {"location": {"s3": self.s3_location.to_dict()}}
319+
320+
321+
@dataclass
322+
class ProfileConfiguration:
323+
"""Profile configuration for persisting browser state across sessions.
324+
325+
Attributes:
326+
profile_identifier: Identifier for the browser profile
327+
"""
328+
329+
profile_identifier: str
330+
331+
def to_dict(self) -> Dict:
332+
"""Convert to API-compatible dictionary."""
333+
return {"profileIdentifier": self.profile_identifier}
334+
335+
336+
@dataclass
337+
class SessionConfiguration:
338+
"""Complete session configuration for start().
339+
340+
Bundles all session-level parameters into one composable type.
341+
Usage: client.start(**session_config.to_dict())
342+
343+
Attributes:
344+
viewport: Viewport dimensions for the browser session
345+
proxy: Proxy configuration for routing browser traffic
346+
extensions: Browser extensions to load into the session
347+
profile: Profile configuration for persisting browser state
348+
"""
349+
350+
viewport: Optional[ViewportConfiguration] = None
351+
proxy: Optional[ProxyConfiguration] = None
352+
extensions: Optional[List[BrowserExtension]] = None
353+
profile: Optional[ProfileConfiguration] = None
354+
355+
def to_dict(self) -> Dict:
356+
"""Convert to API-compatible dictionary."""
357+
config = {}
358+
if self.viewport:
359+
config["viewport"] = self.viewport.to_dict()
360+
if self.proxy:
361+
config["proxy_configuration"] = self.proxy.to_dict()
362+
if self.extensions:
363+
config["extensions"] = [e.to_dict() for e in self.extensions]
364+
if self.profile:
365+
config["profile_configuration"] = self.profile.to_dict()
366+
return config
367+
368+
201369
@dataclass
202370
class BrowserConfiguration:
203371
"""Complete browser configuration for create_browser.

0 commit comments

Comments
 (0)