Skip to content

Commit 199f592

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 1bd22b7 commit 199f592

7 files changed

Lines changed: 670 additions & 4 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.1] - 2026-02-17
49

510
### Fixed

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: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@
44
from .code_interpreter_client import CodeInterpreter, code_session
55
from .config import (
66
BrowserConfiguration,
7+
BrowserExtension,
78
BrowserSigningConfiguration,
89
CodeInterpreterConfiguration,
10+
ExtensionS3Location,
11+
ExternalProxy,
912
NetworkConfiguration,
13+
ProxyConfiguration,
14+
ProxyCredentials,
1015
RecordingConfiguration,
16+
SessionConfiguration,
1117
ViewportConfiguration,
1218
VpcConfig,
1319
create_browser_config,
@@ -19,10 +25,16 @@
1925
"CodeInterpreter",
2026
"code_session",
2127
"BrowserConfiguration",
28+
"BrowserExtension",
2229
"BrowserSigningConfiguration",
2330
"CodeInterpreterConfiguration",
31+
"ExtensionS3Location",
32+
"ExternalProxy",
2433
"NetworkConfiguration",
34+
"ProxyConfiguration",
35+
"ProxyCredentials",
2536
"RecordingConfiguration",
37+
"SessionConfiguration",
2638
"ViewportConfiguration",
2739
"VpcConfig",
2840
"create_browser_config",

src/bedrock_agentcore/tools/browser_client.py

Lines changed: 61 additions & 2 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
1515
from urllib.parse import urlparse
1616

1717
import boto3
@@ -289,6 +289,9 @@ def start(
289289
name: Optional[str] = None,
290290
session_timeout_seconds: Optional[int] = DEFAULT_SESSION_TIMEOUT,
291291
viewport: Optional[Dict[str, int]] = None,
292+
proxy_configuration: Optional[Dict[str, Any]] = None,
293+
extensions: Optional[List[Dict[str, Any]]] = None,
294+
profile_configuration: Optional[Dict[str, Any]] = None,
292295
) -> str:
293296
"""Start a browser sandbox session.
294297
@@ -302,6 +305,15 @@ def start(
302305
Range: 1-28800 (8 hours). Default: 3600 (1 hour).
303306
viewport (Optional[Dict[str, int]]): The viewport dimensions:
304307
{'width': 1920, 'height': 1080}
308+
proxy_configuration (Optional[Dict[str, Any]]): Proxy configuration for
309+
routing browser traffic through external proxy servers. See
310+
https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/browser-proxies.html
311+
extensions (Optional[List[Dict[str, Any]]]): List of browser extensions to
312+
load into the session. Each extension specifies an S3 location:
313+
[{"location": {"s3": {"bucket": "...", "prefix": "..."}}}]
314+
profile_configuration (Optional[Dict[str, Any]]): Profile configuration for
315+
persisting browser state across sessions:
316+
{"profileIdentifier": "my-profile-id"}
305317
306318
Returns:
307319
str: The session ID of the newly created session.
@@ -316,6 +328,20 @@ def start(
316328
... viewport={'width': 1920, 'height': 1080},
317329
... session_timeout_seconds=7200 # 2 hours
318330
... )
331+
>>>
332+
>>> # Use proxy configuration
333+
>>> session_id = client.start(
334+
... proxy_configuration={
335+
... "proxies": [{
336+
... "externalProxy": {
337+
... "server": "proxy.example.com",
338+
... "port": 8080,
339+
... "domainPatterns": [".example.com"],
340+
... }
341+
... }],
342+
... "bypass": {"domainPatterns": [".amazonaws.com"]}
343+
... }
344+
... )
319345
"""
320346
self.logger.info("Starting browser session...")
321347

@@ -328,6 +354,15 @@ def start(
328354
if viewport is not None:
329355
request_params["viewPort"] = viewport
330356

357+
if proxy_configuration is not None:
358+
request_params["proxyConfiguration"] = proxy_configuration
359+
360+
if extensions is not None:
361+
request_params["extensions"] = extensions
362+
363+
if profile_configuration is not None:
364+
request_params["profileConfiguration"] = profile_configuration
365+
331366
response = self.data_plane_client.start_browser_session(**request_params)
332367

333368
self.identifier = response["browserIdentifier"]
@@ -581,14 +616,25 @@ def release_control(self):
581616

582617
@contextmanager
583618
def browser_session(
584-
region: str, viewport: Optional[Dict[str, int]] = None, identifier: Optional[str] = None
619+
region: str,
620+
viewport: Optional[Dict[str, int]] = None,
621+
identifier: Optional[str] = None,
622+
proxy_configuration: Optional[Dict[str, Any]] = None,
623+
extensions: Optional[List[Dict[str, Any]]] = None,
624+
profile_configuration: Optional[Dict[str, Any]] = None,
585625
) -> Generator[BrowserClient, None, None]:
586626
"""Context manager for creating and managing a browser sandbox session.
587627
588628
Args:
589629
region (str): AWS region.
590630
viewport (Optional[Dict[str, int]]): Viewport dimensions.
591631
identifier (Optional[str]): Browser identifier (system or custom).
632+
proxy_configuration (Optional[Dict[str, Any]]): Proxy configuration for
633+
routing browser traffic through external proxy servers.
634+
extensions (Optional[List[Dict[str, Any]]]): List of browser extensions
635+
to load into the session.
636+
profile_configuration (Optional[Dict[str, Any]]): Profile configuration
637+
for persisting browser state across sessions.
592638
593639
Yields:
594640
BrowserClient: An initialized and started browser client.
@@ -602,13 +648,26 @@ def browser_session(
602648
>>> with browser_session('us-west-2', identifier='my-signed-browser') as client:
603649
... # Automation with reduced CAPTCHA friction
604650
... pass
651+
...
652+
>>> # Use proxy configuration
653+
>>> with browser_session('us-west-2', proxy_configuration={
654+
... "proxies": [{"externalProxy": {"server": "proxy.corp.com", "port": 8080}}],
655+
... "bypass": {"domainPatterns": [".amazonaws.com"]}
656+
... }) as client:
657+
... ws_url, headers = client.generate_ws_headers()
605658
"""
606659
client = BrowserClient(region)
607660
start_kwargs = {}
608661
if viewport is not None:
609662
start_kwargs["viewport"] = viewport
610663
if identifier is not None:
611664
start_kwargs["identifier"] = identifier
665+
if proxy_configuration is not None:
666+
start_kwargs["proxy_configuration"] = proxy_configuration
667+
if extensions is not None:
668+
start_kwargs["extensions"] = extensions
669+
if profile_configuration is not None:
670+
start_kwargs["profile_configuration"] = profile_configuration
612671

613672
client.start(**start_kwargs)
614673

src/bedrock_agentcore/tools/config.py

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

200200

201+
@dataclass
202+
class ProxyCredentials:
203+
"""Credentials for authenticating with a proxy server."""
204+
205+
secret_arn: str
206+
207+
def to_dict(self) -> Dict:
208+
"""Convert to API-compatible dictionary."""
209+
return {"basicAuth": {"secretArn": self.secret_arn}}
210+
211+
212+
@dataclass
213+
class ExternalProxy:
214+
"""Configuration for an external proxy server."""
215+
216+
server: str
217+
port: int
218+
domain_patterns: Optional[List[str]] = None
219+
credentials: Optional[ProxyCredentials] = None
220+
221+
def to_dict(self) -> Dict:
222+
"""Convert to API-compatible dictionary."""
223+
proxy = {"server": self.server, "port": self.port}
224+
if self.domain_patterns:
225+
proxy["domainPatterns"] = self.domain_patterns
226+
if self.credentials:
227+
proxy["credentials"] = self.credentials.to_dict()
228+
return {"externalProxy": proxy}
229+
230+
231+
@dataclass
232+
class ProxyConfiguration:
233+
"""Proxy configuration for routing browser traffic through external proxy servers."""
234+
235+
proxies: List[ExternalProxy]
236+
bypass_patterns: Optional[List[str]] = None
237+
238+
def to_dict(self) -> Dict:
239+
"""Convert to API-compatible dictionary."""
240+
config = {"proxies": [p.to_dict() for p in self.proxies]}
241+
if self.bypass_patterns:
242+
config["bypass"] = {"domainPatterns": self.bypass_patterns}
243+
return config
244+
245+
246+
@dataclass
247+
class ExtensionS3Location:
248+
"""S3 location for a browser extension."""
249+
250+
bucket: str
251+
prefix: str
252+
version_id: Optional[str] = None
253+
254+
def to_dict(self) -> Dict:
255+
"""Convert to API-compatible dictionary."""
256+
location = {"bucket": self.bucket, "prefix": self.prefix}
257+
if self.version_id:
258+
location["versionId"] = self.version_id
259+
return location
260+
261+
262+
@dataclass
263+
class BrowserExtension:
264+
"""A browser extension to load into a session."""
265+
266+
s3_location: ExtensionS3Location
267+
268+
def to_dict(self) -> Dict:
269+
"""Convert to API-compatible dictionary."""
270+
return {"location": {"s3": self.s3_location.to_dict()}}
271+
272+
273+
@dataclass
274+
class SessionConfiguration:
275+
"""Complete session configuration for start().
276+
277+
Bundles all session-level parameters into one composable type.
278+
Usage: client.start(**session_config.to_dict())
279+
"""
280+
281+
viewport: Optional[ViewportConfiguration] = None
282+
proxy: Optional[ProxyConfiguration] = None
283+
extensions: Optional[List[BrowserExtension]] = None
284+
profile_identifier: Optional[str] = None
285+
286+
def to_dict(self) -> Dict:
287+
"""Convert to kwargs dict for start()."""
288+
config = {}
289+
if self.viewport:
290+
config["viewport"] = self.viewport.to_dict()
291+
if self.proxy:
292+
config["proxy_configuration"] = self.proxy.to_dict()
293+
if self.extensions:
294+
config["extensions"] = [e.to_dict() for e in self.extensions]
295+
if self.profile_identifier:
296+
config["profile_configuration"] = {
297+
"profileIdentifier": self.profile_identifier
298+
}
299+
return config
300+
301+
201302
@dataclass
202303
class BrowserConfiguration:
203304
"""Complete browser configuration for create_browser.

0 commit comments

Comments
 (0)