bedrock-agentcore-starter-toolkit 0.1.26__py3-none-any.whl → 0.1.27__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 bedrock-agentcore-starter-toolkit might be problematic. Click here for more details.
- bedrock_agentcore_starter_toolkit/cli/cli.py +9 -1
- bedrock_agentcore_starter_toolkit/cli/runtime/commands.py +250 -7
- bedrock_agentcore_starter_toolkit/cli/runtime/configuration_manager.py +31 -7
- bedrock_agentcore_starter_toolkit/notebook/runtime/bedrock_agentcore.py +240 -2
- bedrock_agentcore_starter_toolkit/operations/memory/manager.py +20 -33
- bedrock_agentcore_starter_toolkit/operations/memory/models/strategies/base.py +2 -0
- bedrock_agentcore_starter_toolkit/operations/memory/models/strategies/self_managed.py +107 -0
- bedrock_agentcore_starter_toolkit/operations/runtime/__init__.py +4 -0
- bedrock_agentcore_starter_toolkit/operations/runtime/configure.py +120 -5
- bedrock_agentcore_starter_toolkit/operations/runtime/invoke.py +0 -53
- bedrock_agentcore_starter_toolkit/operations/runtime/launch.py +213 -16
- bedrock_agentcore_starter_toolkit/operations/runtime/models.py +19 -0
- bedrock_agentcore_starter_toolkit/operations/runtime/status.py +30 -0
- bedrock_agentcore_starter_toolkit/operations/runtime/stop_session.py +123 -0
- bedrock_agentcore_starter_toolkit/operations/runtime/vpc_validation.py +196 -0
- bedrock_agentcore_starter_toolkit/services/runtime.py +43 -1
- bedrock_agentcore_starter_toolkit/utils/runtime/schema.py +44 -1
- {bedrock_agentcore_starter_toolkit-0.1.26.dist-info → bedrock_agentcore_starter_toolkit-0.1.27.dist-info}/METADATA +8 -8
- {bedrock_agentcore_starter_toolkit-0.1.26.dist-info → bedrock_agentcore_starter_toolkit-0.1.27.dist-info}/RECORD +23 -20
- {bedrock_agentcore_starter_toolkit-0.1.26.dist-info → bedrock_agentcore_starter_toolkit-0.1.27.dist-info}/WHEEL +0 -0
- {bedrock_agentcore_starter_toolkit-0.1.26.dist-info → bedrock_agentcore_starter_toolkit-0.1.27.dist-info}/entry_points.txt +0 -0
- {bedrock_agentcore_starter_toolkit-0.1.26.dist-info → bedrock_agentcore_starter_toolkit-0.1.27.dist-info}/licenses/LICENSE.txt +0 -0
- {bedrock_agentcore_starter_toolkit-0.1.26.dist-info → bedrock_agentcore_starter_toolkit-0.1.27.dist-info}/licenses/NOTICE.txt +0 -0
|
@@ -22,6 +22,10 @@ class ConfigureResult(BaseModel):
|
|
|
22
22
|
ecr_repository: Optional[str] = Field(None, description="ECR repository URI")
|
|
23
23
|
auto_create_ecr: bool = Field(False, description="Whether ECR will be auto-created")
|
|
24
24
|
memory_id: Optional[str] = Field(default=None, description="Memory resource ID if created")
|
|
25
|
+
network_mode: Optional[str] = Field(None, description="Network mode (PUBLIC or VPC)")
|
|
26
|
+
network_subnets: Optional[List[str]] = Field(None, description="VPC subnet IDs")
|
|
27
|
+
network_security_groups: Optional[List[str]] = Field(None, description="VPC security group IDs")
|
|
28
|
+
network_vpc_id: Optional[str] = Field(None, description="VPC ID")
|
|
25
29
|
|
|
26
30
|
|
|
27
31
|
# Launch operation models
|
|
@@ -70,12 +74,18 @@ class StatusConfigInfo(BaseModel):
|
|
|
70
74
|
ecr_repository: Optional[str] = Field(None, description="ECR repository URI")
|
|
71
75
|
agent_id: Optional[str] = Field(None, description="BedrockAgentCore agent ID")
|
|
72
76
|
agent_arn: Optional[str] = Field(None, description="BedrockAgentCore agent ARN")
|
|
77
|
+
network_mode: Optional[str] = None
|
|
78
|
+
network_subnets: Optional[List[str]] = None
|
|
79
|
+
network_security_groups: Optional[List[str]] = None
|
|
80
|
+
network_vpc_id: Optional[str] = None
|
|
73
81
|
memory_id: Optional[str] = Field(None, description="Memory resource ID")
|
|
74
82
|
memory_status: Optional[str] = Field(None, description="Memory provisioning status (CREATING/ACTIVE/FAILED)")
|
|
75
83
|
memory_type: Optional[str] = Field(None, description="Memory type (STM or STM+LTM)")
|
|
76
84
|
memory_enabled: Optional[bool] = Field(None, description="Whether memory is enabled")
|
|
77
85
|
memory_strategies: Optional[List[str]] = Field(None, description="Active memory strategies")
|
|
78
86
|
memory_details: Optional[Dict[str, Any]] = Field(None, description="Detailed memory resource information")
|
|
87
|
+
idle_timeout: Optional[int] = Field(None, description="Idle runtime session timeout in seconds")
|
|
88
|
+
max_lifetime: Optional[int] = Field(None, description="Maximum instance lifetime in seconds")
|
|
79
89
|
|
|
80
90
|
|
|
81
91
|
class StatusResult(BaseModel):
|
|
@@ -94,3 +104,12 @@ class DestroyResult(BaseModel):
|
|
|
94
104
|
warnings: List[str] = Field(default_factory=list, description="List of warnings during destruction")
|
|
95
105
|
errors: List[str] = Field(default_factory=list, description="List of errors during destruction")
|
|
96
106
|
dry_run: bool = Field(default=False, description="Whether this was a dry run")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class StopSessionResult(BaseModel):
|
|
110
|
+
"""Result of stop session operation."""
|
|
111
|
+
|
|
112
|
+
session_id: str = Field(..., description="Session ID that was stopped")
|
|
113
|
+
agent_name: str = Field(..., description="Name of the agent")
|
|
114
|
+
status_code: int = Field(..., description="HTTP status code of the operation")
|
|
115
|
+
message: str = Field(default="Session stopped successfully", description="Result message")
|
|
@@ -26,6 +26,24 @@ def get_status(config_path: Path, agent_name: Optional[str] = None) -> StatusRes
|
|
|
26
26
|
project_config = load_config(config_path)
|
|
27
27
|
agent_config = project_config.get_agent_config(agent_name)
|
|
28
28
|
|
|
29
|
+
# ADD NETWORK CONFIGURATION EXTRACTION
|
|
30
|
+
network_mode = agent_config.aws.network_configuration.network_mode
|
|
31
|
+
vpc_id = None
|
|
32
|
+
|
|
33
|
+
if network_mode == "VPC" and agent_config.aws.network_configuration.network_mode_config:
|
|
34
|
+
network_config = agent_config.aws.network_configuration.network_mode_config
|
|
35
|
+
|
|
36
|
+
# Try to get VPC ID from subnets (best effort - don't fail if can't retrieve)
|
|
37
|
+
try:
|
|
38
|
+
import boto3
|
|
39
|
+
|
|
40
|
+
ec2_client = boto3.client("ec2", region_name=agent_config.aws.region)
|
|
41
|
+
subnet_response = ec2_client.describe_subnets(SubnetIds=network_config.subnets[:1])
|
|
42
|
+
if subnet_response["Subnets"]:
|
|
43
|
+
vpc_id = subnet_response["Subnets"][0]["VpcId"]
|
|
44
|
+
except Exception:
|
|
45
|
+
pass # nosec B110 # Ignore errors - VPC ID is nice-to-have
|
|
46
|
+
|
|
29
47
|
# Build config info
|
|
30
48
|
config_info = StatusConfigInfo(
|
|
31
49
|
name=agent_config.name,
|
|
@@ -36,8 +54,20 @@ def get_status(config_path: Path, agent_name: Optional[str] = None) -> StatusRes
|
|
|
36
54
|
ecr_repository=agent_config.aws.ecr_repository,
|
|
37
55
|
agent_id=agent_config.bedrock_agentcore.agent_id,
|
|
38
56
|
agent_arn=agent_config.bedrock_agentcore.agent_arn,
|
|
57
|
+
network_mode=agent_config.aws.network_configuration.network_mode,
|
|
58
|
+
network_subnets=agent_config.aws.network_configuration.network_mode_config.subnets
|
|
59
|
+
if agent_config.aws.network_configuration.network_mode_config
|
|
60
|
+
else None,
|
|
61
|
+
network_security_groups=agent_config.aws.network_configuration.network_mode_config.security_groups
|
|
62
|
+
if agent_config.aws.network_configuration.network_mode_config
|
|
63
|
+
else None,
|
|
64
|
+
network_vpc_id=vpc_id,
|
|
39
65
|
)
|
|
40
66
|
|
|
67
|
+
if agent_config.aws.lifecycle_configuration.has_custom_settings:
|
|
68
|
+
config_info.idle_timeout = agent_config.aws.lifecycle_configuration.idle_runtime_session_timeout
|
|
69
|
+
config_info.max_lifetime = agent_config.aws.lifecycle_configuration.max_lifetime
|
|
70
|
+
|
|
41
71
|
# Check if memory is disabled first
|
|
42
72
|
if agent_config.memory and agent_config.memory.mode == "NO_MEMORY":
|
|
43
73
|
config_info.memory_type = "Disabled"
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Stop session operation - terminates active runtime sessions."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from botocore.exceptions import ClientError
|
|
8
|
+
|
|
9
|
+
from ...services.runtime import BedrockAgentCoreClient
|
|
10
|
+
from ...utils.runtime.config import load_config, save_config
|
|
11
|
+
from ...utils.runtime.schema import BedrockAgentCoreAgentSchema, BedrockAgentCoreConfigSchema
|
|
12
|
+
from .models import StopSessionResult
|
|
13
|
+
|
|
14
|
+
log = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def stop_runtime_session(
|
|
18
|
+
config_path: Path,
|
|
19
|
+
session_id: Optional[str] = None,
|
|
20
|
+
agent_name: Optional[str] = None,
|
|
21
|
+
) -> StopSessionResult:
|
|
22
|
+
"""Stop an active runtime session.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
config_path: Path to BedrockAgentCore configuration file
|
|
26
|
+
session_id: Session ID to stop (if None, uses tracked session from config)
|
|
27
|
+
agent_name: Name of agent (for project configurations)
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
StopSessionResult with operation details
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
ValueError: If no session ID provided or found, or agent not deployed
|
|
34
|
+
FileNotFoundError: If configuration file doesn't exist
|
|
35
|
+
"""
|
|
36
|
+
# Load project configuration
|
|
37
|
+
project_config = load_config(config_path)
|
|
38
|
+
agent_config = project_config.get_agent_config(agent_name)
|
|
39
|
+
|
|
40
|
+
log.info("Stopping session for agent: %s", agent_config.name)
|
|
41
|
+
|
|
42
|
+
# Check if agent is deployed
|
|
43
|
+
if not agent_config.bedrock_agentcore.agent_arn:
|
|
44
|
+
raise ValueError(
|
|
45
|
+
f"Agent '{agent_config.name}' is not deployed. Run 'agentcore launch' to deploy the agent first."
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Determine session ID to stop
|
|
49
|
+
target_session_id = session_id
|
|
50
|
+
if not target_session_id:
|
|
51
|
+
# Try to use tracked session from config
|
|
52
|
+
target_session_id = agent_config.bedrock_agentcore.agent_session_id
|
|
53
|
+
if not target_session_id:
|
|
54
|
+
raise ValueError(
|
|
55
|
+
"No active session found. Please provide --session-id or invoke the agent first to create a session."
|
|
56
|
+
)
|
|
57
|
+
log.info("Using tracked session ID from config: %s", target_session_id)
|
|
58
|
+
else:
|
|
59
|
+
log.info("Using provided session ID: %s", target_session_id)
|
|
60
|
+
|
|
61
|
+
region = agent_config.aws.region
|
|
62
|
+
agent_arn = agent_config.bedrock_agentcore.agent_arn
|
|
63
|
+
|
|
64
|
+
# Stop the session
|
|
65
|
+
client = BedrockAgentCoreClient(region)
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
response = client.stop_runtime_session(
|
|
69
|
+
agent_arn=agent_arn,
|
|
70
|
+
session_id=target_session_id,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
status_code = response.get("statusCode", 200)
|
|
74
|
+
|
|
75
|
+
# Success case
|
|
76
|
+
log.info("Session stopped successfully: %s", target_session_id)
|
|
77
|
+
|
|
78
|
+
# Clear the session ID from config if it matches
|
|
79
|
+
if agent_config.bedrock_agentcore.agent_session_id == target_session_id:
|
|
80
|
+
_clear_session_from_config(agent_config, project_config, config_path)
|
|
81
|
+
|
|
82
|
+
return StopSessionResult(
|
|
83
|
+
session_id=target_session_id,
|
|
84
|
+
agent_name=agent_config.name,
|
|
85
|
+
status_code=status_code,
|
|
86
|
+
message="Session stopped successfully",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
except ClientError as e:
|
|
90
|
+
# Case 2: Error propagated as ClientError (defense in depth)
|
|
91
|
+
error_code = e.response.get("Error", {}).get("Code", "")
|
|
92
|
+
error_message = e.response.get("Error", {}).get("Message", "")
|
|
93
|
+
status_code = e.response.get("ResponseMetadata", {}).get("HTTPStatusCode", 500)
|
|
94
|
+
|
|
95
|
+
if error_code in ["ResourceNotFoundException", "NotFound"]:
|
|
96
|
+
log.warning("Session not found (may have already been terminated): %s", target_session_id)
|
|
97
|
+
|
|
98
|
+
# Still clear from config if it matches
|
|
99
|
+
if agent_config.bedrock_agentcore.agent_session_id == target_session_id:
|
|
100
|
+
_clear_session_from_config(agent_config, project_config, config_path)
|
|
101
|
+
|
|
102
|
+
return StopSessionResult(
|
|
103
|
+
session_id=target_session_id,
|
|
104
|
+
agent_name=agent_config.name,
|
|
105
|
+
status_code=404,
|
|
106
|
+
message="Session not found (may have already been terminated)",
|
|
107
|
+
)
|
|
108
|
+
else:
|
|
109
|
+
# Re-raise other client errors
|
|
110
|
+
log.error("Failed to stop session %s: %s - %s", target_session_id, error_code, error_message)
|
|
111
|
+
raise
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _clear_session_from_config(
|
|
115
|
+
agent_config: BedrockAgentCoreAgentSchema,
|
|
116
|
+
project_config: BedrockAgentCoreConfigSchema,
|
|
117
|
+
config_path: Path,
|
|
118
|
+
) -> None:
|
|
119
|
+
"""Clear session ID from agent configuration."""
|
|
120
|
+
agent_config.bedrock_agentcore.agent_session_id = None
|
|
121
|
+
project_config.agents[agent_config.name] = agent_config
|
|
122
|
+
save_config(project_config, config_path)
|
|
123
|
+
log.info("Cleared session ID from configuration")
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""VPC networking validation utilities for AgentCore Runtime."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import List, Optional, Tuple
|
|
5
|
+
|
|
6
|
+
import boto3
|
|
7
|
+
from botocore.exceptions import ClientError
|
|
8
|
+
|
|
9
|
+
log = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def validate_vpc_configuration(
|
|
13
|
+
region: str,
|
|
14
|
+
subnets: List[str],
|
|
15
|
+
security_groups: List[str],
|
|
16
|
+
session: Optional[boto3.Session] = None,
|
|
17
|
+
) -> Tuple[str, List[str]]:
|
|
18
|
+
"""Validate VPC configuration and return VPC ID and any warnings.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
region: AWS region
|
|
22
|
+
subnets: List of subnet IDs
|
|
23
|
+
security_groups: List of security group IDs
|
|
24
|
+
session: Optional boto3 session (creates new if not provided)
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Tuple of (vpc_id, warnings_list)
|
|
28
|
+
|
|
29
|
+
Raises:
|
|
30
|
+
ValueError: If validation fails
|
|
31
|
+
"""
|
|
32
|
+
if not session:
|
|
33
|
+
session = boto3.Session(region_name=region)
|
|
34
|
+
|
|
35
|
+
ec2_client = session.client("ec2", region_name=region)
|
|
36
|
+
warnings = []
|
|
37
|
+
|
|
38
|
+
# Validate subnets
|
|
39
|
+
vpc_id = _validate_subnets(ec2_client, subnets, warnings)
|
|
40
|
+
|
|
41
|
+
# Validate security groups
|
|
42
|
+
_validate_security_groups(ec2_client, security_groups, vpc_id, warnings)
|
|
43
|
+
|
|
44
|
+
return vpc_id, warnings
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _validate_subnets(ec2_client, subnets: List[str], warnings: List[str]) -> str:
|
|
48
|
+
"""Validate subnets and return VPC ID."""
|
|
49
|
+
try:
|
|
50
|
+
response = ec2_client.describe_subnets(SubnetIds=subnets)
|
|
51
|
+
|
|
52
|
+
if len(response["Subnets"]) != len(subnets):
|
|
53
|
+
found_ids = {s["SubnetId"] for s in response["Subnets"]}
|
|
54
|
+
missing = set(subnets) - found_ids
|
|
55
|
+
raise ValueError(f"Subnet IDs not found: {missing}")
|
|
56
|
+
|
|
57
|
+
# Check all subnets are in same VPC
|
|
58
|
+
vpc_ids = {subnet["VpcId"] for subnet in response["Subnets"]}
|
|
59
|
+
|
|
60
|
+
if len(vpc_ids) > 1:
|
|
61
|
+
raise ValueError(
|
|
62
|
+
f"All subnets must be in the same VPC. Found subnets in {len(vpc_ids)} different VPCs: {vpc_ids}"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
vpc_id = vpc_ids.pop()
|
|
66
|
+
log.info("✓ Validated %d subnets in VPC %s", len(subnets), vpc_id)
|
|
67
|
+
|
|
68
|
+
# Check subnet availability zones
|
|
69
|
+
azs = {subnet["AvailabilityZone"] for subnet in response["Subnets"]}
|
|
70
|
+
if len(azs) < 2:
|
|
71
|
+
warnings.append(
|
|
72
|
+
f"Subnets are in only {len(azs)} availability zone(s). "
|
|
73
|
+
"For high availability, use subnets in multiple AZs."
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
return vpc_id
|
|
77
|
+
|
|
78
|
+
except ClientError as e:
|
|
79
|
+
error_code = e.response["Error"]["Code"]
|
|
80
|
+
if error_code == "InvalidSubnetID.NotFound":
|
|
81
|
+
raise ValueError(f"One or more subnet IDs not found: {subnets}") from e
|
|
82
|
+
raise ValueError(f"Failed to validate subnets: {e}") from e
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _validate_security_groups(
|
|
86
|
+
ec2_client, security_groups: List[str], expected_vpc_id: str, warnings: List[str]
|
|
87
|
+
) -> None:
|
|
88
|
+
"""Validate security groups are in the expected VPC."""
|
|
89
|
+
try:
|
|
90
|
+
response = ec2_client.describe_security_groups(GroupIds=security_groups)
|
|
91
|
+
|
|
92
|
+
if len(response["SecurityGroups"]) != len(security_groups):
|
|
93
|
+
found_ids = {sg["GroupId"] for sg in response["SecurityGroups"]}
|
|
94
|
+
missing = set(security_groups) - found_ids
|
|
95
|
+
raise ValueError(f"Security group IDs not found: {missing}")
|
|
96
|
+
|
|
97
|
+
# Check all SGs are in same VPC
|
|
98
|
+
sg_vpcs = {sg["VpcId"] for sg in response["SecurityGroups"]}
|
|
99
|
+
|
|
100
|
+
if len(sg_vpcs) > 1:
|
|
101
|
+
raise ValueError(
|
|
102
|
+
f"All security groups must be in the same VPC. "
|
|
103
|
+
f"Found security groups in {len(sg_vpcs)} different VPCs: {sg_vpcs}"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
sg_vpc_id = sg_vpcs.pop()
|
|
107
|
+
|
|
108
|
+
# Check SGs are in same VPC as subnets
|
|
109
|
+
if sg_vpc_id != expected_vpc_id:
|
|
110
|
+
raise ValueError(
|
|
111
|
+
f"Security groups must be in the same VPC as subnets. "
|
|
112
|
+
f"Subnets are in VPC {expected_vpc_id}, "
|
|
113
|
+
f"but security groups are in VPC {sg_vpc_id}"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
log.info("✓ Validated %d security groups in VPC %s", len(security_groups), sg_vpc_id)
|
|
117
|
+
|
|
118
|
+
except ClientError as e:
|
|
119
|
+
error_code = e.response["Error"]["Code"]
|
|
120
|
+
if error_code == "InvalidGroup.NotFound":
|
|
121
|
+
raise ValueError(f"One or more security group IDs not found: {security_groups}") from e
|
|
122
|
+
raise ValueError(f"Failed to validate security groups: {e}") from e
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def check_network_immutability(
|
|
126
|
+
existing_network_mode: str,
|
|
127
|
+
existing_subnets: Optional[List[str]],
|
|
128
|
+
existing_security_groups: Optional[List[str]],
|
|
129
|
+
new_network_mode: str,
|
|
130
|
+
new_subnets: Optional[List[str]],
|
|
131
|
+
new_security_groups: Optional[List[str]],
|
|
132
|
+
) -> Optional[str]:
|
|
133
|
+
"""Check if network configuration is being changed (not allowed).
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Error message if change detected, None if no change
|
|
137
|
+
"""
|
|
138
|
+
# Check mode change
|
|
139
|
+
if existing_network_mode != new_network_mode:
|
|
140
|
+
return (
|
|
141
|
+
f"Cannot change network mode from {existing_network_mode} to {new_network_mode}. "
|
|
142
|
+
f"Network configuration is immutable after agent creation. "
|
|
143
|
+
f"Create a new agent for different network settings."
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# If both PUBLIC, no further checks needed
|
|
147
|
+
if existing_network_mode == "PUBLIC":
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
# Check VPC resource changes
|
|
151
|
+
if set(existing_subnets or []) != set(new_subnets or []):
|
|
152
|
+
return (
|
|
153
|
+
"Cannot change VPC subnets after agent creation. "
|
|
154
|
+
"Network configuration is immutable. "
|
|
155
|
+
"Create a new agent for different network settings."
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
if set(existing_security_groups or []) != set(new_security_groups or []):
|
|
159
|
+
return (
|
|
160
|
+
"Cannot change VPC security groups after agent creation. "
|
|
161
|
+
"Network configuration is immutable. "
|
|
162
|
+
"Create a new agent for different network settings."
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def verify_subnet_azs(ec2_client, subnets: List[str], region: str) -> List[str]:
|
|
169
|
+
"""Verify subnets are in supported AZs and return any issues."""
|
|
170
|
+
# Supported AZ IDs for us-west-2
|
|
171
|
+
SUPPORTED_AZS = {
|
|
172
|
+
"us-west-2": ["usw2-az1", "usw2-az2", "usw2-az3"],
|
|
173
|
+
"us-east-1": ["use1-az1", "use1-az2", "use1-az4"],
|
|
174
|
+
# Add other regions as needed
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
supported = SUPPORTED_AZS.get(region, [])
|
|
178
|
+
|
|
179
|
+
response = ec2_client.describe_subnets(SubnetIds=subnets)
|
|
180
|
+
issues = []
|
|
181
|
+
|
|
182
|
+
for subnet in response["Subnets"]:
|
|
183
|
+
subnet_id = subnet["SubnetId"]
|
|
184
|
+
az_id = subnet["AvailabilityZoneId"]
|
|
185
|
+
az_name = subnet["AvailabilityZone"]
|
|
186
|
+
|
|
187
|
+
if supported and az_id not in supported:
|
|
188
|
+
issues.append(
|
|
189
|
+
f"Subnet {subnet_id} is in AZ {az_name} (ID: {az_id}) "
|
|
190
|
+
f"which is NOT supported by AgentCore in {region}. "
|
|
191
|
+
f"Supported AZ IDs: {supported}"
|
|
192
|
+
)
|
|
193
|
+
else:
|
|
194
|
+
log.info("✓ Subnet %s is in supported AZ: %s (%s)", subnet_id, az_name, az_id)
|
|
195
|
+
|
|
196
|
+
return issues
|
|
@@ -128,6 +128,7 @@ class BedrockAgentCoreClient:
|
|
|
128
128
|
protocol_config: Optional[Dict] = None,
|
|
129
129
|
env_vars: Optional[Dict] = None,
|
|
130
130
|
auto_update_on_conflict: bool = False,
|
|
131
|
+
lifecycle_config: Optional[Dict] = None,
|
|
131
132
|
) -> Dict[str, str]:
|
|
132
133
|
"""Create new agent."""
|
|
133
134
|
self.logger.info("Creating agent '%s' with image URI: %s", agent_name, image_uri)
|
|
@@ -154,17 +155,20 @@ class BedrockAgentCoreClient:
|
|
|
154
155
|
if env_vars is not None:
|
|
155
156
|
params["environmentVariables"] = env_vars
|
|
156
157
|
|
|
158
|
+
if lifecycle_config is not None:
|
|
159
|
+
params["lifecycleConfiguration"] = lifecycle_config
|
|
160
|
+
|
|
157
161
|
resp = self.client.create_agent_runtime(**params)
|
|
158
162
|
agent_id = resp["agentRuntimeId"]
|
|
159
163
|
agent_arn = resp["agentRuntimeArn"]
|
|
160
164
|
self.logger.info("Successfully created agent '%s' with ID: %s, ARN: %s", agent_name, agent_id, agent_arn)
|
|
161
165
|
return {"id": agent_id, "arn": agent_arn}
|
|
166
|
+
|
|
162
167
|
except ClientError as e:
|
|
163
168
|
error_code = e.response.get("Error", {}).get("Code")
|
|
164
169
|
if error_code == "ConflictException":
|
|
165
170
|
if not auto_update_on_conflict:
|
|
166
171
|
self.logger.error("Agent '%s' already exists and auto_update_on_conflict is disabled", agent_name)
|
|
167
|
-
# Create a more helpful error message
|
|
168
172
|
raise ClientError(
|
|
169
173
|
{
|
|
170
174
|
"Error": {
|
|
@@ -226,6 +230,7 @@ class BedrockAgentCoreClient:
|
|
|
226
230
|
request_header_config: Optional[Dict] = None,
|
|
227
231
|
protocol_config: Optional[Dict] = None,
|
|
228
232
|
env_vars: Optional[Dict] = None,
|
|
233
|
+
lifecycle_config: Optional[Dict] = None,
|
|
229
234
|
) -> Dict[str, str]:
|
|
230
235
|
"""Update existing agent."""
|
|
231
236
|
self.logger.info("Updating agent ID '%s' with image URI: %s", agent_id, image_uri)
|
|
@@ -252,6 +257,9 @@ class BedrockAgentCoreClient:
|
|
|
252
257
|
if env_vars is not None:
|
|
253
258
|
params["environmentVariables"] = env_vars
|
|
254
259
|
|
|
260
|
+
if lifecycle_config is not None:
|
|
261
|
+
params["lifecycleConfiguration"] = lifecycle_config
|
|
262
|
+
|
|
255
263
|
resp = self.client.update_agent_runtime(**params)
|
|
256
264
|
agent_arn = resp["agentRuntimeArn"]
|
|
257
265
|
self.logger.info("Successfully updated agent ID '%s', ARN: %s", agent_id, agent_arn)
|
|
@@ -312,6 +320,7 @@ class BedrockAgentCoreClient:
|
|
|
312
320
|
protocol_config: Optional[Dict] = None,
|
|
313
321
|
env_vars: Optional[Dict] = None,
|
|
314
322
|
auto_update_on_conflict: bool = False,
|
|
323
|
+
lifecycle_config: Optional[Dict] = None,
|
|
315
324
|
) -> Dict[str, str]:
|
|
316
325
|
"""Create or update agent."""
|
|
317
326
|
if agent_id:
|
|
@@ -324,6 +333,7 @@ class BedrockAgentCoreClient:
|
|
|
324
333
|
request_header_config,
|
|
325
334
|
protocol_config,
|
|
326
335
|
env_vars,
|
|
336
|
+
lifecycle_config,
|
|
327
337
|
)
|
|
328
338
|
return self.create_agent(
|
|
329
339
|
agent_name,
|
|
@@ -335,6 +345,7 @@ class BedrockAgentCoreClient:
|
|
|
335
345
|
protocol_config,
|
|
336
346
|
env_vars,
|
|
337
347
|
auto_update_on_conflict,
|
|
348
|
+
lifecycle_config,
|
|
338
349
|
)
|
|
339
350
|
|
|
340
351
|
def wait_for_agent_endpoint_ready(self, agent_id: str, endpoint_name: str = "DEFAULT", max_wait: int = 120) -> str:
|
|
@@ -483,6 +494,37 @@ class BedrockAgentCoreClient:
|
|
|
483
494
|
"before-sign.bedrock-agentcore.InvokeAgentRuntime", handler_id
|
|
484
495
|
)
|
|
485
496
|
|
|
497
|
+
def stop_runtime_session(
|
|
498
|
+
self,
|
|
499
|
+
agent_arn: str,
|
|
500
|
+
session_id: str,
|
|
501
|
+
endpoint_name: str = "DEFAULT",
|
|
502
|
+
) -> Dict:
|
|
503
|
+
"""Stop a runtime session.
|
|
504
|
+
|
|
505
|
+
Args:
|
|
506
|
+
agent_arn: Agent ARN
|
|
507
|
+
session_id: Session ID to stop
|
|
508
|
+
endpoint_name: Endpoint name, defaults to "DEFAULT"
|
|
509
|
+
|
|
510
|
+
Returns:
|
|
511
|
+
Response with status code
|
|
512
|
+
|
|
513
|
+
Raises:
|
|
514
|
+
ClientError: If the operation fails, including ResourceNotFoundException
|
|
515
|
+
if the session doesn't exist
|
|
516
|
+
"""
|
|
517
|
+
self.logger.info("Stopping runtime session: %s", session_id)
|
|
518
|
+
|
|
519
|
+
response = self.dataplane_client.stop_runtime_session(
|
|
520
|
+
agentRuntimeArn=agent_arn,
|
|
521
|
+
qualifier=endpoint_name,
|
|
522
|
+
runtimeSessionId=session_id,
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
self.logger.info("Successfully stopped session: %s", session_id)
|
|
526
|
+
return response
|
|
527
|
+
|
|
486
528
|
|
|
487
529
|
class HttpBedrockAgentCoreClient:
|
|
488
530
|
"""Bedrock AgentCore client for agent management using HTTP requests with bearer token."""
|
|
@@ -16,7 +16,7 @@ class MemoryConfig(BaseModel):
|
|
|
16
16
|
"""Memory configuration for BedrockAgentCore."""
|
|
17
17
|
|
|
18
18
|
mode: Literal["STM_ONLY", "STM_AND_LTM", "NO_MEMORY"] = Field(
|
|
19
|
-
default="
|
|
19
|
+
default="NO_MEMORY", description="Memory mode - opt-in feature"
|
|
20
20
|
)
|
|
21
21
|
memory_id: Optional[str] = Field(default=None, description="Memory resource ID")
|
|
22
22
|
memory_arn: Optional[str] = Field(default=None, description="Memory resource ARN")
|
|
@@ -99,6 +99,48 @@ class ProtocolConfiguration(BaseModel):
|
|
|
99
99
|
return {"serverProtocol": self.server_protocol}
|
|
100
100
|
|
|
101
101
|
|
|
102
|
+
class LifecycleConfiguration(BaseModel):
|
|
103
|
+
"""Lifecycle configuration for runtime sessions."""
|
|
104
|
+
|
|
105
|
+
idle_runtime_session_timeout: Optional[int] = Field(
|
|
106
|
+
default=None,
|
|
107
|
+
description="Timeout in seconds for idle runtime sessions (60-28800)",
|
|
108
|
+
ge=60,
|
|
109
|
+
le=28800,
|
|
110
|
+
)
|
|
111
|
+
max_lifetime: Optional[int] = Field(
|
|
112
|
+
default=None, description="Maximum lifetime for the instance in seconds (60-28800)", ge=60, le=28800
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
@field_validator("max_lifetime")
|
|
116
|
+
@classmethod
|
|
117
|
+
def validate_lifecycle_relationship(cls, v: Optional[int], info) -> Optional[int]:
|
|
118
|
+
"""Validate that max_lifetime >= idle_timeout if both are set."""
|
|
119
|
+
if v is None:
|
|
120
|
+
return v
|
|
121
|
+
|
|
122
|
+
idle = info.data.get("idle_runtime_session_timeout")
|
|
123
|
+
if idle is not None and v < idle:
|
|
124
|
+
raise ValueError(
|
|
125
|
+
f"max_lifetime ({v}s) must be greater than or equal to idle_runtime_session_timeout ({idle}s)"
|
|
126
|
+
)
|
|
127
|
+
return v
|
|
128
|
+
|
|
129
|
+
def to_aws_dict(self) -> dict:
|
|
130
|
+
"""Convert to AWS API format with camelCase keys."""
|
|
131
|
+
result = {}
|
|
132
|
+
if self.idle_runtime_session_timeout is not None:
|
|
133
|
+
result["idleRuntimeSessionTimeout"] = self.idle_runtime_session_timeout
|
|
134
|
+
if self.max_lifetime is not None:
|
|
135
|
+
result["maxLifetime"] = self.max_lifetime
|
|
136
|
+
return result
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def has_custom_settings(self) -> bool:
|
|
140
|
+
"""Check if any custom lifecycle settings are configured."""
|
|
141
|
+
return self.idle_runtime_session_timeout is not None or self.max_lifetime is not None
|
|
142
|
+
|
|
143
|
+
|
|
102
144
|
class ObservabilityConfig(BaseModel):
|
|
103
145
|
"""Observability configuration."""
|
|
104
146
|
|
|
@@ -117,6 +159,7 @@ class AWSConfig(BaseModel):
|
|
|
117
159
|
network_configuration: NetworkConfiguration = Field(default_factory=NetworkConfiguration)
|
|
118
160
|
protocol_configuration: ProtocolConfiguration = Field(default_factory=ProtocolConfiguration)
|
|
119
161
|
observability: ObservabilityConfig = Field(default_factory=ObservabilityConfig)
|
|
162
|
+
lifecycle_configuration: LifecycleConfiguration = Field(default_factory=LifecycleConfiguration)
|
|
120
163
|
|
|
121
164
|
@field_validator("account")
|
|
122
165
|
@classmethod
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bedrock-agentcore-starter-toolkit
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.27
|
|
4
4
|
Summary: A starter toolkit for using Bedrock AgentCore
|
|
5
5
|
Project-URL: Homepage, https://github.com/aws/bedrock-agentcore-starter-toolkit
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/aws/bedrock-agentcore-starter-toolkit/issues
|
|
@@ -83,38 +83,38 @@ Amazon Bedrock AgentCore includes the following modular Services that you can us
|
|
|
83
83
|
## 🚀 Amazon Bedrock AgentCore Runtime
|
|
84
84
|
AgentCore Runtime is a secure, serverless runtime purpose-built for deploying and scaling dynamic AI agents and tools using any open-source framework including LangGraph, CrewAI, and Strands Agents, any protocol, and any model. Runtime was built to work for agentic workloads with industry-leading extended runtime support, fast cold starts, true session isolation, built-in identity, and support for multi-modal payloads. Developers can focus on innovation while Amazon Bedrock AgentCore Runtime handles infrastructure and security -- accelerating time-to-market
|
|
85
85
|
|
|
86
|
-
**[Runtime Quick Start](https://aws.
|
|
86
|
+
**[Runtime Quick Start](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-get-started-toolkit.html)**
|
|
87
87
|
|
|
88
88
|
## 🧠 Amazon Bedrock AgentCore Memory
|
|
89
89
|
AgentCore Memory makes it easy for developers to build context aware agents by eliminating complex memory infrastructure management while providing full control over what the AI agent remembers. Memory provides industry-leading accuracy along with support for both short-term memory for multi-turn conversations and long-term memory that can be shared across agents and sessions.
|
|
90
90
|
|
|
91
91
|
|
|
92
|
-
**[Memory Quick Start](https://aws.
|
|
92
|
+
**[Memory Quick Start](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/memory-get-started.html)**
|
|
93
93
|
|
|
94
94
|
## 🔗 Amazon Bedrock AgentCore Gateway
|
|
95
95
|
Amazon Bedrock AgentCore Gateway acts as a managed Model Context Protocol (MCP) server that converts APIs and Lambda functions into MCP tools that agents can use. Gateway manages the complexity of OAuth ingress authorization and secure egress credential exchange, making standing up remote MCP servers easier and more secure. Gateway also offers composition and built-in semantic search over tools, enabling developers to scale their agents to use hundreds or thousands of tools.
|
|
96
96
|
|
|
97
|
-
**[Gateway Quick Start](https://aws.
|
|
97
|
+
**[Gateway Quick Start](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-quick-start.html)**
|
|
98
98
|
|
|
99
99
|
## 💻 Amazon Bedrock AgentCore Code Interpreter
|
|
100
100
|
AgentCore Code Interpreter tool enables agents to securely execute code in isolated sandbox environments. It offers advanced configuration support and seamless integration with popular frameworks. Developers can build powerful agents for complex workflows and data analysis while meeting enterprise security requirements.
|
|
101
101
|
|
|
102
|
-
**[Code Interpreter Quick Start](https://aws.
|
|
102
|
+
**[Code Interpreter Quick Start](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/code-interpreter-getting-started.html)**
|
|
103
103
|
|
|
104
104
|
## 🌐 Amazon Bedrock AgentCore Browser
|
|
105
105
|
AgentCore Browser tool provides a fast, secure, cloud-based browser runtime to enable AI agents to interact with websites at scale. It provides enterprise-grade security, comprehensive observability features, and automatically scales— all without infrastructure management overhead.
|
|
106
106
|
|
|
107
|
-
**[Browser Quick Start](https://aws.
|
|
107
|
+
**[Browser Quick Start](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/browser-onboarding.html)**
|
|
108
108
|
|
|
109
109
|
## 📊 Amazon Bedrock AgentCore Observability
|
|
110
110
|
AgentCore Observability helps developers trace, debug, and monitor agent performance in production through unified operational dashboards. With support for OpenTelemetry compatible telemetry and detailed visualizations of each step of the agent workflow, AgentCore enables developers to easily gain visibility into agent behavior and maintain quality standards at scale.
|
|
111
111
|
|
|
112
|
-
**[Observability Quick Start](https://aws.
|
|
112
|
+
**[Observability Quick Start](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/observability-get-started.html)**
|
|
113
113
|
|
|
114
114
|
## 🔐 Amazon Bedrock AgentCore Identity
|
|
115
115
|
AgentCore Identity provides a secure, scalable agent identity and access management capability accelerating AI agent development. It is compatible with existing identity providers, eliminating needs for user migration or rebuilding authentication flows. AgentCore Identity's helps to minimize consent fatigue with a secure token vault and allows you to build streamlined AI agent experiences. Just-enough access and secure permission delegation allow agents to securely access AWS resources and third-party tools and services.
|
|
116
116
|
|
|
117
|
-
**[Identity Quick Start](https://aws.
|
|
117
|
+
**[Identity Quick Start](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/identity-getting-started-cognito.html)**
|
|
118
118
|
|
|
119
119
|
## 🔐 Import Amazon Bedrock Agents to Bedrock AgentCore
|
|
120
120
|
AgentCore Import-Agent enables seamless migration of existing Amazon Bedrock Agents to LangChain/LangGraph or Strands frameworks while automatically integrating AgentCore primitives like Memory, Code Interpreter, and Gateway. Developers can migrate agents in minutes with full feature parity and deploy directly to AgentCore Runtime for serverless operation.
|