|
1 | 1 | import logging |
| 2 | +import uuid |
2 | 3 | from dataclasses import dataclass |
3 | 4 | from typing import Any, Dict, List, Optional |
4 | 5 |
|
@@ -42,88 +43,59 @@ class SessionInfo: |
42 | 43 |
|
43 | 44 |
|
44 | 45 | class AgentCoreCodeInterpreter(CodeInterpreter): |
45 | | - """ |
46 | | - Bedrock AgentCore implementation of the CodeInterpreter. |
47 | | -
|
48 | | - This class provides a code interpreter interface using AWS Bedrock AgentCore services. |
49 | | - It supports executing Python, JavaScript, and TypeScript code in isolated sandbox |
50 | | - environments with custom code interpreter identifiers. |
51 | | -
|
52 | | - The class maintains session state and provides methods for code execution, file |
53 | | - operations, and session management. It supports both default AWS code interpreter |
54 | | - environments and custom environments specified by identifier. |
55 | | -
|
56 | | - Examples: |
57 | | - Basic usage with default identifier: |
58 | | -
|
59 | | - >>> interpreter = AgentCoreCodeInterpreter(region="us-west-2") |
60 | | - >>> # Uses default identifier: "aws.codeinterpreter.v1" |
61 | | -
|
62 | | - Using a custom code interpreter identifier: |
63 | | -
|
64 | | - >>> custom_id = "my-custom-interpreter-abc123" |
65 | | - >>> interpreter = AgentCoreCodeInterpreter( |
66 | | - ... region="us-west-2", |
67 | | - ... identifier=custom_id |
68 | | - ... ) |
69 | | -
|
70 | | - Environment-specific usage: |
71 | | -
|
72 | | - >>> # For testing environments |
73 | | - >>> test_interpreter = AgentCoreCodeInterpreter( |
74 | | - ... region="us-east-1", |
75 | | - ... identifier="test-interpreter-xyz789" |
76 | | - ... ) |
77 | | -
|
78 | | - >>> # For production environments |
79 | | - >>> prod_interpreter = AgentCoreCodeInterpreter( |
80 | | - ... region="us-west-2", |
81 | | - ... identifier="prod-interpreter-def456" |
82 | | - ... ) |
83 | | -
|
84 | | - Attributes: |
85 | | - region (str): The AWS region where the code interpreter service is hosted. |
86 | | - identifier (str): The code interpreter identifier being used for sessions. |
87 | | - """ |
88 | | - |
89 | | - def __init__(self, region: Optional[str] = None, identifier: Optional[str] = None) -> None: |
| 46 | + def __init__( |
| 47 | + self, |
| 48 | + region: Optional[str] = None, |
| 49 | + identifier: Optional[str] = None, |
| 50 | + session_name: Optional[str] = None, |
| 51 | + auto_create: bool = True, |
| 52 | + ) -> None: |
90 | 53 | """ |
91 | 54 | Initialize the Bedrock AgentCore code interpreter. |
92 | 55 |
|
93 | 56 | Args: |
94 | | - region (Optional[str]): AWS region for the sandbox service. If not provided, |
95 | | - the region will be resolved from AWS configuration (environment variables, |
96 | | - AWS config files, or instance metadata). Defaults to None. |
97 | | - identifier (Optional[str]): Custom code interpreter identifier to use |
98 | | - for code execution sessions. This allows you to specify custom code |
99 | | - interpreter environments instead of the default AWS-provided one. |
100 | | -
|
101 | | - Valid formats include: |
102 | | - - Default identifier: "aws.codeinterpreter.v1" (used when None) |
103 | | - - Custom identifier: "my-custom-interpreter-abc123" |
104 | | - - Environment-specific: "test-interpreter-xyz789" |
105 | | -
|
106 | | - Note: Use the code interpreter ID, not the full ARN. The AWS service |
107 | | - expects the identifier portion only (e.g., "my-interpreter-123" rather |
108 | | - than "arn:aws:bedrock-agentcore:region:account:code-interpreter-custom/my-interpreter-123"). |
109 | | -
|
110 | | - If not provided, defaults to "aws.codeinterpreter.v1" for backward |
111 | | - compatibility. Defaults to None. |
112 | | -
|
113 | | - Note: |
114 | | - This constructor maintains full backward compatibility. Existing code |
115 | | - that doesn't specify the identifier parameter will continue to work |
116 | | - unchanged with the default AWS code interpreter environment. |
117 | | -
|
118 | | - Raises: |
119 | | - Exception: If there are issues with AWS region resolution or client |
120 | | - initialization during session creation. |
| 57 | + region: AWS region for the sandbox service. |
| 58 | + identifier: Custom code interpreter identifier. |
| 59 | + session_name: Session name or strategy: |
| 60 | + - None (default): Generate random session ID per instance |
| 61 | + - "runtime": Use AgentCore runtime session_id (set at invocation time) |
| 62 | + - Specific string: Use this exact session name |
| 63 | + auto_create: Automatically create sessions if they don't exist. |
| 64 | + - True (default): Create sessions on-demand |
| 65 | + - False: Fail if session doesn't exist (strict mode) |
| 66 | +
|
| 67 | + Examples: |
| 68 | + # Case 1: Random session per instance (default) |
| 69 | + interpreter = AgentCoreCodeInterpreter() |
| 70 | +
|
| 71 | + # Case 2: Bind to runtime session (recommended for production) |
| 72 | + session_id = getattr(context, 'session_id', None) |
| 73 | + interpreter = AgentCoreCodeInterpreter(session_name=session_id) |
| 74 | +
|
| 75 | + # Case 3: Named session with auto-create |
| 76 | + interpreter = AgentCoreCodeInterpreter(session_name="my-analysis") |
| 77 | +
|
| 78 | + # Case 4: Strict mode - must pre-initialize |
| 79 | + interpreter = AgentCoreCodeInterpreter( |
| 80 | + session_name="must-exist", |
| 81 | + auto_create=False |
| 82 | + ) |
121 | 83 | """ |
122 | 84 | super().__init__() |
123 | 85 | self.region = resolve_region(region) |
124 | 86 | self.identifier = identifier or "aws.codeinterpreter.v1" |
| 87 | + self.auto_create = auto_create |
| 88 | + |
| 89 | + # Generate session name strategy |
| 90 | + if session_name is None: |
| 91 | + self.default_session = f"session-{uuid.uuid4().hex[:12]}" |
| 92 | + else: |
| 93 | + self.default_session = session_name |
| 94 | + |
125 | 95 | self._sessions: Dict[str, SessionInfo] = {} |
126 | 96 |
|
| 97 | + logger.info(f"Initialized CodeInterpreter with session='{self.default_session}', " f"auto_create={auto_create}") |
| 98 | + |
127 | 99 | def start_platform(self) -> None: |
128 | 100 | """Initialize the Bedrock AgentCoreplatform connection.""" |
129 | 101 | pass |
@@ -240,84 +212,127 @@ def list_local_sessions(self) -> Dict[str, Any]: |
240 | 212 | "content": [{"json": {"sessions": sessions_info, "totalSessions": len(sessions_info)}}], |
241 | 213 | } |
242 | 214 |
|
| 215 | + def _ensure_session(self, session_name: Optional[str]) -> tuple[str, Optional[Dict[str, Any]]]: |
| 216 | + """ |
| 217 | + Ensure a session exists based on configuration. |
| 218 | +
|
| 219 | + Behavior matrix: |
| 220 | + | session_name | auto_create | Behavior | |
| 221 | + |--------------|-------------|----------| |
| 222 | + | None | True | Use default_session, create if needed | |
| 223 | + | None | False | Use default_session, error if missing | |
| 224 | + | "my-session" | True | Use "my-session", create if needed | |
| 225 | + | "my-session" | False | Use "my-session", error if missing | |
| 226 | +
|
| 227 | + Args: |
| 228 | + session_name: Explicit session name from action, or None to use default |
| 229 | +
|
| 230 | + Returns: |
| 231 | + Tuple of (session_name, error_dict or None) |
| 232 | + """ |
| 233 | + # Determine which session to use |
| 234 | + target_session = session_name if session_name else self.default_session |
| 235 | + |
| 236 | + # Session already exists - use it |
| 237 | + if target_session in self._sessions: |
| 238 | + logger.debug(f"Using existing session: {target_session}") |
| 239 | + return target_session, None |
| 240 | + |
| 241 | + # Session doesn't exist - check auto_create |
| 242 | + if self.auto_create: |
| 243 | + # Auto-create the session |
| 244 | + logger.info(f"Auto-creating session: {target_session}") |
| 245 | + init_action = InitSessionAction( |
| 246 | + type="initSession", session_name=target_session, description="Auto-initialized session" |
| 247 | + ) |
| 248 | + result = self.init_session(init_action) |
| 249 | + |
| 250 | + if result.get("status") != "success": |
| 251 | + return target_session, result |
| 252 | + |
| 253 | + logger.info(f"Successfully auto-created session: {target_session}") |
| 254 | + return target_session, None |
| 255 | + |
| 256 | + # auto_create=False and session doesn't exist - raise exception |
| 257 | + logger.debug(f"Session '{target_session}' not found (auto_create disabled)") |
| 258 | + raise ValueError(f"Session '{target_session}' not found. Create it first using initSession.") |
| 259 | + |
243 | 260 | def execute_code(self, action: ExecuteCodeAction) -> Dict[str, Any]: |
244 | | - """Execute code in a Bedrock AgentCoresession.""" |
245 | | - if action.session_name not in self._sessions: |
246 | | - return {"status": "error", "content": [{"text": f"Session '{action.session_name}' not found"}]} |
| 261 | + """Execute code in a Bedrock AgentCore session with automatic session management.""" |
| 262 | + session_name, error = self._ensure_session(action.session_name) |
| 263 | + if error: |
| 264 | + return error |
247 | 265 |
|
248 | | - logger.debug(f"Executing {action.language} code in session '{action.session_name}'") |
| 266 | + logger.debug(f"Executing {action.language} code in session '{session_name}'") |
249 | 267 |
|
250 | | - # Use the invoke method with proper parameters as shown in the example |
251 | 268 | params = {"code": action.code, "language": action.language.value, "clearContext": action.clear_context} |
252 | | - response = self._sessions[action.session_name].client.invoke("executeCode", params) |
| 269 | + response = self._sessions[session_name].client.invoke("executeCode", params) |
253 | 270 |
|
254 | 271 | return self._create_tool_result(response) |
255 | 272 |
|
256 | 273 | def execute_command(self, action: ExecuteCommandAction) -> Dict[str, Any]: |
257 | | - """Execute a command in a Bedrock AgentCoresession.""" |
258 | | - if action.session_name not in self._sessions: |
259 | | - return {"status": "error", "content": [{"text": f"Session '{action.session_name}' not found"}]} |
| 274 | + """Execute a command in a Bedrock AgentCore session with automatic session management.""" |
| 275 | + session_name, error = self._ensure_session(action.session_name) |
| 276 | + if error: |
| 277 | + return error |
260 | 278 |
|
261 | | - logger.debug(f"Executing command in session '{action.session_name}': {action.command}") |
| 279 | + logger.debug(f"Executing command in session '{session_name}': {action.command}") |
262 | 280 |
|
263 | | - # Use the invoke method with proper parameters as shown in the example |
264 | 281 | params = {"command": action.command} |
265 | | - response = self._sessions[action.session_name].client.invoke("executeCommand", params) |
| 282 | + response = self._sessions[session_name].client.invoke("executeCommand", params) |
266 | 283 |
|
267 | 284 | return self._create_tool_result(response) |
268 | 285 |
|
269 | 286 | def read_files(self, action: ReadFilesAction) -> Dict[str, Any]: |
270 | | - """Read files from a Bedrock AgentCoresession.""" |
271 | | - if action.session_name not in self._sessions: |
272 | | - return {"status": "error", "content": [{"text": f"Session '{action.session_name}' not found"}]} |
| 287 | + """Read files from a Bedrock AgentCore session with automatic session management.""" |
| 288 | + session_name, error = self._ensure_session(action.session_name) |
| 289 | + if error: |
| 290 | + return error |
273 | 291 |
|
274 | | - logger.debug(f"Reading files from session '{action.session_name}': {action.paths}") |
| 292 | + logger.debug(f"Reading files from session '{session_name}': {action.paths}") |
275 | 293 |
|
276 | | - # Use the invoke method with proper parameters as shown in the example |
277 | 294 | params = {"paths": action.paths} |
278 | | - response = self._sessions[action.session_name].client.invoke("readFiles", params) |
| 295 | + response = self._sessions[session_name].client.invoke("readFiles", params) |
279 | 296 |
|
280 | 297 | return self._create_tool_result(response) |
281 | 298 |
|
282 | 299 | def list_files(self, action: ListFilesAction) -> Dict[str, Any]: |
283 | | - """List files in a Bedrock AgentCoresession directory.""" |
284 | | - if action.session_name not in self._sessions: |
285 | | - return {"status": "error", "content": [{"text": f"Session '{action.session_name}' not found"}]} |
| 300 | + """List files in a Bedrock AgentCore session directory with automatic session management.""" |
| 301 | + session_name, error = self._ensure_session(action.session_name) |
| 302 | + if error: |
| 303 | + return error |
286 | 304 |
|
287 | | - logger.debug(f"Listing files in session '{action.session_name}' at path: {action.path}") |
| 305 | + logger.debug(f"Listing files in session '{session_name}' at path: {action.path}") |
288 | 306 |
|
289 | | - # Use the invoke method with proper parameters as shown in the example |
290 | 307 | params = {"path": action.path} |
291 | | - response = self._sessions[action.session_name].client.invoke("listFiles", params) |
| 308 | + response = self._sessions[session_name].client.invoke("listFiles", params) |
292 | 309 |
|
293 | 310 | return self._create_tool_result(response) |
294 | 311 |
|
295 | 312 | def remove_files(self, action: RemoveFilesAction) -> Dict[str, Any]: |
296 | | - """Remove files from a Bedrock AgentCoresession.""" |
297 | | - if action.session_name not in self._sessions: |
298 | | - return {"status": "error", "content": [{"text": f"Session '{action.session_name}' not found"}]} |
| 313 | + """Remove files from a Bedrock AgentCore session with automatic session management.""" |
| 314 | + session_name, error = self._ensure_session(action.session_name) |
| 315 | + if error: |
| 316 | + return error |
299 | 317 |
|
300 | | - logger.debug(f"Removing files from session '{action.session_name}': {action.paths}") |
| 318 | + logger.debug(f"Removing files from session '{session_name}': {action.paths}") |
301 | 319 |
|
302 | | - # Use the invoke method with proper parameters as shown in the example |
303 | 320 | params = {"paths": action.paths} |
304 | | - response = self._sessions[action.session_name].client.invoke("removeFiles", params) |
| 321 | + response = self._sessions[session_name].client.invoke("removeFiles", params) |
305 | 322 |
|
306 | 323 | return self._create_tool_result(response) |
307 | 324 |
|
308 | 325 | def write_files(self, action: WriteFilesAction) -> Dict[str, Any]: |
309 | | - """Write files to a Bedrock AgentCoresession.""" |
310 | | - if action.session_name not in self._sessions: |
311 | | - return {"status": "error", "content": [{"text": f"Session '{action.session_name}' not found"}]} |
| 326 | + """Write files to a Bedrock AgentCore session with automatic session management.""" |
| 327 | + session_name, error = self._ensure_session(action.session_name) |
| 328 | + if error: |
| 329 | + return error |
312 | 330 |
|
313 | | - logger.debug(f"Writing {len(action.content)} files to session '{action.session_name}'") |
| 331 | + logger.debug(f"Writing {len(action.content)} files to session '{session_name}'") |
314 | 332 |
|
315 | | - # Convert FileContent objects to dictionaries for the API |
316 | 333 | content_dicts = [{"path": fc.path, "text": fc.text} for fc in action.content] |
317 | | - |
318 | | - # Use the invoke method with proper parameters as shown in the example |
319 | 334 | params = {"content": content_dicts} |
320 | | - response = self._sessions[action.session_name].client.invoke("writeFiles", params) |
| 335 | + response = self._sessions[session_name].client.invoke("writeFiles", params) |
321 | 336 |
|
322 | 337 | return self._create_tool_result(response) |
323 | 338 |
|
|
0 commit comments