agentrun-sdk 0.1.2__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 agentrun-sdk might be problematic. Click here for more details.

Files changed (115) hide show
  1. agentrun_operation_sdk/cli/__init__.py +1 -0
  2. agentrun_operation_sdk/cli/cli.py +19 -0
  3. agentrun_operation_sdk/cli/common.py +21 -0
  4. agentrun_operation_sdk/cli/runtime/__init__.py +1 -0
  5. agentrun_operation_sdk/cli/runtime/commands.py +203 -0
  6. agentrun_operation_sdk/client/client.py +75 -0
  7. agentrun_operation_sdk/operations/runtime/__init__.py +8 -0
  8. agentrun_operation_sdk/operations/runtime/configure.py +101 -0
  9. agentrun_operation_sdk/operations/runtime/launch.py +82 -0
  10. agentrun_operation_sdk/operations/runtime/models.py +31 -0
  11. agentrun_operation_sdk/services/runtime.py +152 -0
  12. agentrun_operation_sdk/utils/logging_config.py +72 -0
  13. agentrun_operation_sdk/utils/runtime/config.py +94 -0
  14. agentrun_operation_sdk/utils/runtime/container.py +280 -0
  15. agentrun_operation_sdk/utils/runtime/entrypoint.py +203 -0
  16. agentrun_operation_sdk/utils/runtime/schema.py +56 -0
  17. agentrun_sdk/__init__.py +7 -0
  18. agentrun_sdk/agent/__init__.py +25 -0
  19. agentrun_sdk/agent/agent.py +696 -0
  20. agentrun_sdk/agent/agent_result.py +46 -0
  21. agentrun_sdk/agent/conversation_manager/__init__.py +26 -0
  22. agentrun_sdk/agent/conversation_manager/conversation_manager.py +88 -0
  23. agentrun_sdk/agent/conversation_manager/null_conversation_manager.py +46 -0
  24. agentrun_sdk/agent/conversation_manager/sliding_window_conversation_manager.py +179 -0
  25. agentrun_sdk/agent/conversation_manager/summarizing_conversation_manager.py +252 -0
  26. agentrun_sdk/agent/state.py +97 -0
  27. agentrun_sdk/event_loop/__init__.py +9 -0
  28. agentrun_sdk/event_loop/event_loop.py +499 -0
  29. agentrun_sdk/event_loop/streaming.py +319 -0
  30. agentrun_sdk/experimental/__init__.py +4 -0
  31. agentrun_sdk/experimental/hooks/__init__.py +15 -0
  32. agentrun_sdk/experimental/hooks/events.py +123 -0
  33. agentrun_sdk/handlers/__init__.py +10 -0
  34. agentrun_sdk/handlers/callback_handler.py +70 -0
  35. agentrun_sdk/hooks/__init__.py +49 -0
  36. agentrun_sdk/hooks/events.py +80 -0
  37. agentrun_sdk/hooks/registry.py +247 -0
  38. agentrun_sdk/models/__init__.py +10 -0
  39. agentrun_sdk/models/anthropic.py +432 -0
  40. agentrun_sdk/models/bedrock.py +649 -0
  41. agentrun_sdk/models/litellm.py +225 -0
  42. agentrun_sdk/models/llamaapi.py +438 -0
  43. agentrun_sdk/models/mistral.py +539 -0
  44. agentrun_sdk/models/model.py +95 -0
  45. agentrun_sdk/models/ollama.py +357 -0
  46. agentrun_sdk/models/openai.py +436 -0
  47. agentrun_sdk/models/sagemaker.py +598 -0
  48. agentrun_sdk/models/writer.py +449 -0
  49. agentrun_sdk/multiagent/__init__.py +22 -0
  50. agentrun_sdk/multiagent/a2a/__init__.py +15 -0
  51. agentrun_sdk/multiagent/a2a/executor.py +148 -0
  52. agentrun_sdk/multiagent/a2a/server.py +252 -0
  53. agentrun_sdk/multiagent/base.py +92 -0
  54. agentrun_sdk/multiagent/graph.py +555 -0
  55. agentrun_sdk/multiagent/swarm.py +656 -0
  56. agentrun_sdk/py.typed +1 -0
  57. agentrun_sdk/session/__init__.py +18 -0
  58. agentrun_sdk/session/file_session_manager.py +216 -0
  59. agentrun_sdk/session/repository_session_manager.py +152 -0
  60. agentrun_sdk/session/s3_session_manager.py +272 -0
  61. agentrun_sdk/session/session_manager.py +73 -0
  62. agentrun_sdk/session/session_repository.py +51 -0
  63. agentrun_sdk/telemetry/__init__.py +21 -0
  64. agentrun_sdk/telemetry/config.py +194 -0
  65. agentrun_sdk/telemetry/metrics.py +476 -0
  66. agentrun_sdk/telemetry/metrics_constants.py +15 -0
  67. agentrun_sdk/telemetry/tracer.py +563 -0
  68. agentrun_sdk/tools/__init__.py +17 -0
  69. agentrun_sdk/tools/decorator.py +569 -0
  70. agentrun_sdk/tools/executor.py +137 -0
  71. agentrun_sdk/tools/loader.py +152 -0
  72. agentrun_sdk/tools/mcp/__init__.py +13 -0
  73. agentrun_sdk/tools/mcp/mcp_agent_tool.py +99 -0
  74. agentrun_sdk/tools/mcp/mcp_client.py +423 -0
  75. agentrun_sdk/tools/mcp/mcp_instrumentation.py +322 -0
  76. agentrun_sdk/tools/mcp/mcp_types.py +63 -0
  77. agentrun_sdk/tools/registry.py +607 -0
  78. agentrun_sdk/tools/structured_output.py +421 -0
  79. agentrun_sdk/tools/tools.py +217 -0
  80. agentrun_sdk/tools/watcher.py +136 -0
  81. agentrun_sdk/types/__init__.py +5 -0
  82. agentrun_sdk/types/collections.py +23 -0
  83. agentrun_sdk/types/content.py +188 -0
  84. agentrun_sdk/types/event_loop.py +48 -0
  85. agentrun_sdk/types/exceptions.py +81 -0
  86. agentrun_sdk/types/guardrails.py +254 -0
  87. agentrun_sdk/types/media.py +89 -0
  88. agentrun_sdk/types/session.py +152 -0
  89. agentrun_sdk/types/streaming.py +201 -0
  90. agentrun_sdk/types/tools.py +258 -0
  91. agentrun_sdk/types/traces.py +5 -0
  92. agentrun_sdk-0.1.2.dist-info/METADATA +51 -0
  93. agentrun_sdk-0.1.2.dist-info/RECORD +115 -0
  94. agentrun_sdk-0.1.2.dist-info/WHEEL +5 -0
  95. agentrun_sdk-0.1.2.dist-info/entry_points.txt +2 -0
  96. agentrun_sdk-0.1.2.dist-info/top_level.txt +3 -0
  97. agentrun_wrapper/__init__.py +11 -0
  98. agentrun_wrapper/_utils/__init__.py +6 -0
  99. agentrun_wrapper/_utils/endpoints.py +16 -0
  100. agentrun_wrapper/identity/__init__.py +5 -0
  101. agentrun_wrapper/identity/auth.py +211 -0
  102. agentrun_wrapper/memory/__init__.py +6 -0
  103. agentrun_wrapper/memory/client.py +1697 -0
  104. agentrun_wrapper/memory/constants.py +103 -0
  105. agentrun_wrapper/memory/controlplane.py +626 -0
  106. agentrun_wrapper/py.typed +1 -0
  107. agentrun_wrapper/runtime/__init__.py +13 -0
  108. agentrun_wrapper/runtime/app.py +473 -0
  109. agentrun_wrapper/runtime/context.py +34 -0
  110. agentrun_wrapper/runtime/models.py +25 -0
  111. agentrun_wrapper/services/__init__.py +1 -0
  112. agentrun_wrapper/services/identity.py +192 -0
  113. agentrun_wrapper/tools/__init__.py +6 -0
  114. agentrun_wrapper/tools/browser_client.py +325 -0
  115. agentrun_wrapper/tools/code_interpreter_client.py +186 -0
@@ -0,0 +1,192 @@
1
+ """The main high-level client for the Bedrock AgentCore Identity service."""
2
+
3
+ import asyncio
4
+ import logging
5
+ import time
6
+ import uuid
7
+ from abc import ABC, abstractmethod
8
+ from typing import Any, Callable, Dict, List, Literal, Optional
9
+
10
+ import boto3
11
+
12
+ from bedrock_agentcore._utils.endpoints import get_control_plane_endpoint, get_data_plane_endpoint
13
+
14
+
15
+ class TokenPoller(ABC):
16
+ """Abstract base class for token polling implementations."""
17
+
18
+ @abstractmethod
19
+ async def poll_for_token(self) -> str:
20
+ """Poll for a token and return it when available."""
21
+ raise NotImplementedError
22
+
23
+
24
+ # Default configuration for the polling mechanism
25
+ DEFAULT_POLLING_INTERVAL_SECONDS = 5
26
+ DEFAULT_POLLING_TIMEOUT_SECONDS = 600
27
+
28
+
29
+ class _DefaultApiTokenPoller(TokenPoller):
30
+ """Default implementation of token polling."""
31
+
32
+ def __init__(self, auth_url: str, func: Callable[[], str | None]):
33
+ """Initialize the token poller with auth URL and polling function."""
34
+ self.auth_url = auth_url
35
+ self.polling_func = func
36
+ self.logger = logging.getLogger("bedrock_agentcore.default_token_poller")
37
+ self.logger.setLevel("INFO")
38
+ if not self.logger.handlers:
39
+ self.logger.addHandler(logging.StreamHandler())
40
+
41
+ async def poll_for_token(self) -> str:
42
+ """Poll for a token until it becomes available or timeout occurs."""
43
+ start_time = time.time()
44
+ while time.time() - start_time < DEFAULT_POLLING_TIMEOUT_SECONDS:
45
+ await asyncio.sleep(DEFAULT_POLLING_INTERVAL_SECONDS)
46
+
47
+ self.logger.info("Polling for token for authorization url: %s", self.auth_url)
48
+ resp = self.polling_func()
49
+ if resp is not None:
50
+ self.logger.info("Token is ready")
51
+ return resp
52
+
53
+ raise asyncio.TimeoutError(
54
+ f"Polling timed out after {DEFAULT_POLLING_TIMEOUT_SECONDS} seconds. "
55
+ + "User may not have completed authorization."
56
+ )
57
+
58
+
59
+ class IdentityClient:
60
+ """A high-level client for Bedrock AgentCore Identity."""
61
+
62
+ def __init__(self, region: str):
63
+ """Initialize the identity client with the specified region."""
64
+ self.region = region
65
+ self.cp_client = boto3.client(
66
+ "bedrock-agentcore-control", region_name=region, endpoint_url=get_control_plane_endpoint(region)
67
+ )
68
+ self.identity_client = boto3.client(
69
+ "bedrock-agentcore-control", region_name=region, endpoint_url=get_data_plane_endpoint(region)
70
+ )
71
+ self.dp_client = boto3.client(
72
+ "bedrock-agentcore", region_name=region, endpoint_url=get_data_plane_endpoint(region)
73
+ )
74
+ self.logger = logging.getLogger("bedrock_agentcore.identity_client")
75
+
76
+ def create_oauth2_credential_provider(self, req):
77
+ """Create an OAuth2 credential provider."""
78
+ self.logger.info("Creating OAuth2 credential provider...")
79
+ return self.cp_client.create_oauth2_credential_provider(**req)
80
+
81
+ def create_api_key_credential_provider(self, req):
82
+ """Create an API key credential provider."""
83
+ self.logger.info("Creating API key credential provider...")
84
+ return self.cp_client.create_api_key_credential_provider(**req)
85
+
86
+ def get_workload_access_token(
87
+ self, workload_name: str, user_token: Optional[str] = None, user_id: Optional[str] = None
88
+ ) -> Dict:
89
+ """Get a workload access token using workload name and optionally user token."""
90
+ if user_token:
91
+ if user_id is not None:
92
+ self.logger.warning("Both user token and user id are supplied, using user token")
93
+ self.logger.info("Getting workload access token for JWT...")
94
+ resp = self.dp_client.get_workload_access_token_for_jwt(workloadName=workload_name, userToken=user_token)
95
+ elif user_id:
96
+ self.logger.info("Getting workload access token for user id...")
97
+ resp = self.dp_client.get_workload_access_token_for_user_id(workloadName=workload_name, userId=user_id)
98
+ else:
99
+ self.logger.info("Getting workload access token...")
100
+ resp = self.dp_client.get_workload_access_token(workloadName=workload_name)
101
+
102
+ self.logger.info("Successfully retrieved workload access token")
103
+ return resp
104
+
105
+ def create_workload_identity(self, name: Optional[str] = None) -> Dict:
106
+ """Create workload identity with optional name."""
107
+ self.logger.info("Creating workload identity...")
108
+ if not name:
109
+ name = f"workload-{uuid.uuid4().hex[:8]}"
110
+ return self.identity_client.create_workload_identity(name=name)
111
+
112
+ async def get_token(
113
+ self,
114
+ *,
115
+ provider_name: str,
116
+ scopes: Optional[List[str]] = None,
117
+ agent_identity_token: str,
118
+ on_auth_url: Optional[Callable[[str], Any]] = None,
119
+ auth_flow: Literal["M2M", "USER_FEDERATION"],
120
+ callback_url: Optional[str] = None,
121
+ force_authentication: bool = False,
122
+ token_poller: Optional[TokenPoller] = None,
123
+ ) -> str:
124
+ """Get an OAuth2 access token for the specified provider.
125
+
126
+ Args:
127
+ provider_name: The credential provider name
128
+ scopes: Optional list of OAuth2 scopes to request
129
+ agent_identity_token: Agent identity token for authentication
130
+ on_auth_url: Callback for handling authorization URLs
131
+ auth_flow: Authentication flow type ("M2M" or "USER_FEDERATION")
132
+ callback_url: OAuth2 callback URL (must be pre-registered)
133
+ force_authentication: Force re-authentication even if token exists in the token vault
134
+ token_poller: Custom token poller implementation
135
+
136
+ Returns:
137
+ The access token string
138
+
139
+ Raises:
140
+ RequiresUserConsentException: When user consent is needed
141
+ Various other exceptions for error conditions
142
+ """
143
+ self.logger.info("Getting OAuth2 token...")
144
+
145
+ # Build parameters
146
+ req = {
147
+ "resourceCredentialProviderName": provider_name,
148
+ "scopes": scopes,
149
+ "oauth2Flow": auth_flow,
150
+ "workloadIdentityToken": agent_identity_token,
151
+ }
152
+
153
+ # Add optional parameters
154
+ if callback_url:
155
+ req["resourceOauth2ReturnUrl"] = callback_url
156
+ if force_authentication:
157
+ req["forceAuthentication"] = force_authentication
158
+
159
+ response = self.dp_client.get_resource_oauth2_token(**req)
160
+
161
+ # If we got a token directly, return it
162
+ if "accessToken" in response:
163
+ return response["accessToken"]
164
+
165
+ # If we got an authorization URL, handle the OAuth flow
166
+ if "authorizationUrl" in response:
167
+ auth_url = response["authorizationUrl"]
168
+ # Notify about the auth URL if callback provided
169
+ if on_auth_url:
170
+ if asyncio.iscoroutinefunction(on_auth_url):
171
+ await on_auth_url(auth_url)
172
+ else:
173
+ on_auth_url(auth_url)
174
+
175
+ # only the initial request should have force authentication
176
+ if force_authentication:
177
+ req["forceAuthentication"] = False
178
+
179
+ # Poll for the token
180
+ active_poller = token_poller or _DefaultApiTokenPoller(
181
+ auth_url, lambda: self.dp_client.get_resource_oauth2_token(**req).get("accessToken", None)
182
+ )
183
+ return await active_poller.poll_for_token()
184
+
185
+ raise RuntimeError("Identity service did not return a token or an authorization URL.")
186
+
187
+ async def get_api_key(self, *, provider_name: str, agent_identity_token: str) -> str:
188
+ """Programmatically retrieves an API key from the Identity service."""
189
+ self.logger.info("Getting API key...")
190
+ req = {"resourceCredentialProviderName": provider_name, "workloadIdentityToken": agent_identity_token}
191
+
192
+ return self.dp_client.get_resource_api_key(**req)["apiKey"]
@@ -0,0 +1,6 @@
1
+ """Bedrock AgentCore SDK tools package."""
2
+
3
+ from .browser_client import BrowserClient, browser_session
4
+ from .code_interpreter_client import CodeInterpreter, code_session
5
+
6
+ __all__ = ["BrowserClient", "browser_session", "CodeInterpreter", "code_session"]
@@ -0,0 +1,325 @@
1
+ """Client for interacting with the Browser sandbox service.
2
+
3
+ This module provides a client for the AWS Browser sandbox, allowing
4
+ applications to start, stop, and automate browser interactions in a managed
5
+ sandbox environment using Playwright.
6
+ """
7
+
8
+ import base64
9
+ import datetime
10
+ import logging
11
+ import secrets
12
+ import uuid
13
+ from contextlib import contextmanager
14
+ from typing import Dict, Generator, Optional, Tuple
15
+ from urllib.parse import urlparse
16
+
17
+ import boto3
18
+ from botocore.auth import SigV4Auth, SigV4QueryAuth
19
+ from botocore.awsrequest import AWSRequest
20
+
21
+ from .._utils.endpoints import (
22
+ get_data_plane_endpoint,
23
+ )
24
+
25
+ DEFAULT_IDENTIFIER = "aws.browser.v1"
26
+ DEFAULT_SESSION_TIMEOUT = 3600
27
+ DEFAULT_LIVE_VIEW_PRESIGNED_URL_TIMEOUT = 300
28
+
29
+
30
+ class BrowserClient:
31
+ """Client for interacting with the AWS Browser sandbox service.
32
+
33
+ This client handles the session lifecycle and browser automation for
34
+ Browser sandboxes, providing an interface to perform web automation
35
+ tasks in a secure, managed environment.
36
+
37
+ Attributes:
38
+ region (str): The AWS region being used.
39
+ data_plane_service_name (str): AWS service name for the data plane.
40
+ client: The boto3 client for interacting with the service.
41
+ identifier (str, optional): The browser identifier.
42
+ session_id (str, optional): The active session ID.
43
+ """
44
+
45
+ def __init__(self, region: str) -> None:
46
+ """Initialize a Browser client for the specified AWS region.
47
+
48
+ Args:
49
+ region (str): The AWS region to use for the Browser service.
50
+ """
51
+ self.region = region
52
+ self.data_plane_service_name = "bedrock-agentcore"
53
+ self.client = boto3.client(
54
+ self.data_plane_service_name, region_name=region, endpoint_url=get_data_plane_endpoint(region)
55
+ )
56
+ self._identifier = None
57
+ self._session_id = None
58
+ self.logger = logging.getLogger(__name__)
59
+
60
+ @property
61
+ def identifier(self) -> Optional[str]:
62
+ """Get the current browser identifier.
63
+
64
+ Returns:
65
+ Optional[str]: The current identifier or None if not set.
66
+ """
67
+ return self._identifier
68
+
69
+ @identifier.setter
70
+ def identifier(self, value: Optional[str]):
71
+ """Set the browser identifier.
72
+
73
+ Args:
74
+ value (Optional[str]): The identifier to set.
75
+ """
76
+ self._identifier = value
77
+
78
+ @property
79
+ def session_id(self) -> Optional[str]:
80
+ """Get the current session ID.
81
+
82
+ Returns:
83
+ Optional[str]: The current session ID or None if not set.
84
+ """
85
+ return self._session_id
86
+
87
+ @session_id.setter
88
+ def session_id(self, value: Optional[str]):
89
+ """Set the session ID.
90
+
91
+ Args:
92
+ value (Optional[str]): The session ID to set.
93
+ """
94
+ self._session_id = value
95
+
96
+ def start(
97
+ self,
98
+ identifier: Optional[str] = DEFAULT_IDENTIFIER,
99
+ name: Optional[str] = None,
100
+ session_timeout_seconds: Optional[int] = DEFAULT_SESSION_TIMEOUT,
101
+ ) -> str:
102
+ """Start a browser sandbox session.
103
+
104
+ This method initializes a new browser session with the provided parameters.
105
+
106
+ Args:
107
+ identifier (Optional[str]): The browser sandbox identifier to use.
108
+ Defaults to DEFAULT_IDENTIFIER.
109
+ name (Optional[str]): A name for this session. If not provided, a name
110
+ will be generated using a UUID.
111
+ session_timeout_seconds (Optional[int]): The timeout for the session in seconds.
112
+ Defaults to DEFAULT_TIMEOUT.
113
+ description (Optional[str]): A description for this session.
114
+ Defaults to an empty string.
115
+
116
+ Returns:
117
+ str: The session ID of the newly created session.
118
+ """
119
+ self.logger.info("Starting browser session...")
120
+
121
+ response = self.client.start_browser_session(
122
+ browserIdentifier=identifier,
123
+ name=name or f"browser-session-{uuid.uuid4().hex[:8]}",
124
+ sessionTimeoutSeconds=session_timeout_seconds,
125
+ )
126
+
127
+ self.identifier = response["browserIdentifier"]
128
+ self.session_id = response["sessionId"]
129
+
130
+ return self.session_id
131
+
132
+ def stop(self):
133
+ """Stop the current browser session if one is active.
134
+
135
+ This method stops any active session and clears the session state.
136
+ If no session is active, this method does nothing.
137
+
138
+ Returns:
139
+ bool: True if no session was active or the session was successfully stopped.
140
+ """
141
+ self.logger.info("Stopping browser session...")
142
+
143
+ if not self.session_id or not self.identifier:
144
+ return True
145
+
146
+ self.client.stop_browser_session(**{"browserIdentifier": self.identifier, "sessionId": self.session_id})
147
+
148
+ self.identifier = None
149
+ self.session_id = None
150
+
151
+ def generate_ws_headers(self) -> Tuple[str, Dict[str, str]]:
152
+ """Generate the WebSocket headers needed for connecting to the browser sandbox.
153
+
154
+ This method creates properly signed WebSocket headers for connecting to
155
+ the browser automation endpoint.
156
+
157
+ Returns:
158
+ Tuple[str, Dict[str, str]]: A tuple containing the WebSocket URL and
159
+ the headers dictionary.
160
+
161
+ Raises:
162
+ RuntimeError: If no AWS credentials are found.
163
+ """
164
+ self.logger.info("Generating websocket headers...")
165
+
166
+ if not self.identifier or not self.session_id:
167
+ self.start()
168
+
169
+ host = get_data_plane_endpoint(self.region).replace("https://", "")
170
+ path = f"/browser-streams/{self.identifier}/sessions/{self.session_id}/automation"
171
+ ws_url = f"wss://{host}{path}"
172
+
173
+ boto_session = boto3.Session()
174
+ credentials = boto_session.get_credentials()
175
+ if not credentials:
176
+ raise RuntimeError("No AWS credentials found")
177
+
178
+ frozen_credentials = credentials.get_frozen_credentials()
179
+
180
+ request = AWSRequest(
181
+ method="GET",
182
+ url=f"https://{host}{path}",
183
+ headers={
184
+ "host": host,
185
+ "x-amz-date": datetime.datetime.now(datetime.timezone.utc).strftime("%Y%m%dT%H%M%SZ"),
186
+ },
187
+ )
188
+
189
+ auth = SigV4Auth(frozen_credentials, self.data_plane_service_name, self.region)
190
+ auth.add_auth(request)
191
+
192
+ headers = {
193
+ "Host": host,
194
+ "X-Amz-Date": request.headers["x-amz-date"],
195
+ "Authorization": request.headers["Authorization"],
196
+ "Upgrade": "websocket",
197
+ "Connection": "Upgrade",
198
+ "Sec-WebSocket-Version": "13",
199
+ "Sec-WebSocket-Key": base64.b64encode(secrets.token_bytes(16)).decode(),
200
+ "User-Agent": f"BrowserSandbox-Client/1.0 (Session: {self.session_id})",
201
+ }
202
+
203
+ if frozen_credentials.token:
204
+ headers["X-Amz-Security-Token"] = frozen_credentials.token
205
+
206
+ return ws_url, headers
207
+
208
+ def generate_live_view_url(self, expires: int = DEFAULT_LIVE_VIEW_PRESIGNED_URL_TIMEOUT) -> str:
209
+ """Generate a pre-signed URL for viewing the browser session.
210
+
211
+ Creates a pre-signed URL that can be used to view the current browser session.
212
+ If no session is active, a new session will be started.
213
+
214
+ Args:
215
+ expires (int, optional): The number of seconds until the pre-signed URL expires.
216
+ Defaults to DEFAULT_LIVE_VIEW_PRESIGNED_URL_TIMEOUT (300 seconds).
217
+
218
+ Returns:
219
+ str: The pre-signed URL for viewing the browser session.
220
+
221
+ Raises:
222
+ RuntimeError: If the URL generation fails.
223
+ """
224
+ self.logger.info("Generating live view url...")
225
+
226
+ if not self.identifier or not self.session_id:
227
+ self.start()
228
+
229
+ url = urlparse(
230
+ f"{get_data_plane_endpoint(self.region)}/browser-streams/{self.identifier}/sessions/{self.session_id}/live-view"
231
+ )
232
+ boto_session = boto3.Session()
233
+ credentials = boto_session.get_credentials().get_frozen_credentials()
234
+ request = AWSRequest(method="GET", url=url.geturl(), headers={"host": url.hostname})
235
+ signer = SigV4QueryAuth(
236
+ credentials=credentials, service_name=self.data_plane_service_name, region_name=self.region, expires=expires
237
+ )
238
+ signer.add_auth(request)
239
+
240
+ if not request.url:
241
+ raise RuntimeError("Failed to generate live view url")
242
+
243
+ return request.url
244
+
245
+ def take_control(self):
246
+ """Take control of the browser session by disabling the automation stream.
247
+
248
+ This method disables external automation capabilities of the browser session,
249
+ giving this client exclusive control. If no session is active, a new session
250
+ will be started.
251
+
252
+ Raises:
253
+ RuntimeError: If a session could not be found or started.
254
+ """
255
+ self.logger.info("Taking control of browser session...")
256
+
257
+ if not self.identifier or not self.session_id:
258
+ self.start()
259
+
260
+ if not self.identifier or not self.session_id:
261
+ raise RuntimeError("Could not find or start a browser session")
262
+
263
+ self._update_browser_stream(self.identifier, self.session_id, "DISABLED")
264
+
265
+ def release_control(self):
266
+ """Release control of the browser session by enabling the automation stream.
267
+
268
+ This method enables external automation capabilities of the browser session,
269
+ relinquishing exclusive control. If no session exists, a warning is logged
270
+ and the method returns without taking action.
271
+ """
272
+ self.logger.info("Releasing control of browser session...")
273
+
274
+ if not self.identifier or not self.session_id:
275
+ self.logger.warning("Could not find a browser session when releasing control")
276
+ return
277
+
278
+ self._update_browser_stream(self.identifier, self.session_id, "ENABLED")
279
+
280
+ def _update_browser_stream(self, identifier: str, session_id: str, stream_status: str) -> None:
281
+ """Update the browser stream status.
282
+
283
+ This private helper method updates the status of the browser automation stream.
284
+
285
+ Args:
286
+ identifier (str): The browser identifier.
287
+ session_id (str): The session ID.
288
+ stream_status (str): The status to set for the automation stream.
289
+ Valid values are "ENABLED" or "DISABLED".
290
+ """
291
+ self.client.update_browser_stream(
292
+ **{
293
+ "browserIdentifier": identifier,
294
+ "sessionId": session_id,
295
+ "streamUpdate": {"automationStreamUpdate": {"streamStatus": stream_status}},
296
+ }
297
+ )
298
+
299
+
300
+ @contextmanager
301
+ def browser_session(region: str) -> Generator[BrowserClient, None, None]:
302
+ """Context manager for creating and managing a browser sandbox session.
303
+
304
+ This context manager handles creating a client, starting a session, and
305
+ ensuring the session is properly cleaned up when the context exits.
306
+
307
+ Args:
308
+ region (str): The AWS region to use for the Browser service.
309
+
310
+ Yields:
311
+ BrowserClient: An initialized and started browser client.
312
+
313
+ Example:
314
+ >>> with browser_session('us-west-2') as client:
315
+ ... browser = client.get_browser_obj()
316
+ ... page = browser.new_page()
317
+ ... page.goto('https://example.com')
318
+ """
319
+ client = BrowserClient(region)
320
+ client.start()
321
+
322
+ try:
323
+ yield client
324
+ finally:
325
+ client.stop()