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
|
@@ -4,11 +4,13 @@ import logging
|
|
|
4
4
|
import os
|
|
5
5
|
import re
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Any, Dict, Literal, Optional, Tuple
|
|
7
|
+
from typing import Any, Dict, List, Literal, Optional, Tuple
|
|
8
|
+
|
|
9
|
+
import boto3
|
|
8
10
|
|
|
9
11
|
from ...cli.runtime.configuration_manager import ConfigurationManager
|
|
10
12
|
from ...services.ecr import get_account_id, get_region
|
|
11
|
-
from ...utils.runtime.config import merge_agent_config, save_config
|
|
13
|
+
from ...utils.runtime.config import load_config_if_exists, merge_agent_config, save_config
|
|
12
14
|
from ...utils.runtime.container import ContainerRuntime
|
|
13
15
|
from ...utils.runtime.entrypoint import detect_dependencies
|
|
14
16
|
from ...utils.runtime.schema import (
|
|
@@ -16,8 +18,10 @@ from ...utils.runtime.schema import (
|
|
|
16
18
|
BedrockAgentCoreAgentSchema,
|
|
17
19
|
BedrockAgentCoreDeploymentInfo,
|
|
18
20
|
CodeBuildConfig,
|
|
21
|
+
LifecycleConfiguration,
|
|
19
22
|
MemoryConfig,
|
|
20
23
|
NetworkConfiguration,
|
|
24
|
+
NetworkModeConfig,
|
|
21
25
|
ObservabilityConfig,
|
|
22
26
|
ProtocolConfiguration,
|
|
23
27
|
)
|
|
@@ -133,7 +137,7 @@ def configure_bedrock_agentcore(
|
|
|
133
137
|
auto_create_ecr: bool = True,
|
|
134
138
|
auto_create_execution_role: bool = True,
|
|
135
139
|
enable_observability: bool = True,
|
|
136
|
-
memory_mode: Literal["NO_MEMORY", "STM_ONLY", "STM_AND_LTM"] = "
|
|
140
|
+
memory_mode: Literal["NO_MEMORY", "STM_ONLY", "STM_AND_LTM"] = "NO_MEMORY",
|
|
137
141
|
requirements_file: Optional[str] = None,
|
|
138
142
|
authorizer_configuration: Optional[Dict[str, Any]] = None,
|
|
139
143
|
request_header_configuration: Optional[Dict[str, Any]] = None,
|
|
@@ -142,6 +146,11 @@ def configure_bedrock_agentcore(
|
|
|
142
146
|
protocol: Optional[str] = None,
|
|
143
147
|
non_interactive: bool = False,
|
|
144
148
|
source_path: Optional[str] = None,
|
|
149
|
+
vpc_enabled: bool = False,
|
|
150
|
+
vpc_subnets: Optional[List[str]] = None,
|
|
151
|
+
vpc_security_groups: Optional[List[str]] = None,
|
|
152
|
+
idle_timeout: Optional[int] = None,
|
|
153
|
+
max_lifetime: Optional[int] = None,
|
|
145
154
|
) -> ConfigureResult:
|
|
146
155
|
"""Configure Bedrock AgentCore application with deployment settings.
|
|
147
156
|
|
|
@@ -164,6 +173,13 @@ def configure_bedrock_agentcore(
|
|
|
164
173
|
protocol: agent server protocol, must be either HTTP or MCP or A2A
|
|
165
174
|
non_interactive: Skip interactive prompts and use defaults
|
|
166
175
|
source_path: Optional path to agent source code directory
|
|
176
|
+
vpc_enabled: Whether to enable VPC networking mode
|
|
177
|
+
vpc_subnets: List of subnet IDs for VPC mode
|
|
178
|
+
vpc_security_groups: List of security group IDs for VPC mode
|
|
179
|
+
idle_timeout: Idle runtime session timeout in seconds (60-28800).
|
|
180
|
+
If not specified, AWS API default (900s / 15 minutes) is used.
|
|
181
|
+
max_lifetime: Maximum instance lifetime in seconds (60-28800).
|
|
182
|
+
If not specified, AWS API default (28800s / 8 hours) is used.
|
|
167
183
|
|
|
168
184
|
Returns:
|
|
169
185
|
ConfigureResult model with configuration details
|
|
@@ -244,7 +260,7 @@ def configure_bedrock_agentcore(
|
|
|
244
260
|
else: # STM_ONLY
|
|
245
261
|
log.info("Memory configuration: Short-term memory only")
|
|
246
262
|
else:
|
|
247
|
-
# Interactive mode
|
|
263
|
+
# Interactive mode - let user choose
|
|
248
264
|
action, value = config_manager.prompt_memory_selection()
|
|
249
265
|
|
|
250
266
|
if action == "USE_EXISTING":
|
|
@@ -274,6 +290,21 @@ def configure_bedrock_agentcore(
|
|
|
274
290
|
memory_id = None
|
|
275
291
|
memory_name = None
|
|
276
292
|
|
|
293
|
+
# Handle lifecycle configuration
|
|
294
|
+
lifecycle_config = LifecycleConfiguration()
|
|
295
|
+
if idle_timeout is not None or max_lifetime is not None:
|
|
296
|
+
lifecycle_config = LifecycleConfiguration(
|
|
297
|
+
idle_runtime_session_timeout=idle_timeout,
|
|
298
|
+
max_lifetime=max_lifetime,
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
if verbose:
|
|
302
|
+
log.debug("Lifecycle configuration:")
|
|
303
|
+
if idle_timeout:
|
|
304
|
+
log.debug(" Idle timeout: %ds (%d minutes)", idle_timeout, idle_timeout / 60)
|
|
305
|
+
if max_lifetime:
|
|
306
|
+
log.debug(" Max lifetime: %ds (%d hours)", max_lifetime, max_lifetime / 3600)
|
|
307
|
+
|
|
277
308
|
if config_path.exists():
|
|
278
309
|
try:
|
|
279
310
|
from ...utils.runtime.config import load_config
|
|
@@ -305,6 +336,42 @@ def configure_bedrock_agentcore(
|
|
|
305
336
|
if verbose and execution_role_arn:
|
|
306
337
|
log.debug("Using same role for CodeBuild: %s", codebuild_execution_role_arn)
|
|
307
338
|
|
|
339
|
+
if vpc_enabled:
|
|
340
|
+
if not vpc_subnets or not vpc_security_groups:
|
|
341
|
+
raise ValueError("VPC mode requires both subnets and security groups")
|
|
342
|
+
|
|
343
|
+
for subnet_id in vpc_subnets:
|
|
344
|
+
if not subnet_id.startswith("subnet-"):
|
|
345
|
+
raise ValueError(
|
|
346
|
+
f"Invalid subnet ID format: {subnet_id}\nSubnet IDs must start with 'subnet-' (e.g., subnet-abc123)"
|
|
347
|
+
)
|
|
348
|
+
if len(subnet_id) < 15: # "subnet-" (7) + 8 chars = 15
|
|
349
|
+
raise ValueError(
|
|
350
|
+
f"Invalid subnet ID format: {subnet_id}\nSubnet ID is too short. Expected format: subnet-xxxxxxxx"
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
# Validate security group IDs format
|
|
354
|
+
for sg_id in vpc_security_groups:
|
|
355
|
+
if not sg_id.startswith("sg-"):
|
|
356
|
+
raise ValueError(
|
|
357
|
+
f"Invalid security group ID format: {sg_id}\n"
|
|
358
|
+
f"Security group IDs must start with 'sg-' (e.g., sg-abc123)"
|
|
359
|
+
)
|
|
360
|
+
if len(sg_id) < 11: # "sg-" (3) + 8 chars = 11
|
|
361
|
+
raise ValueError(
|
|
362
|
+
f"Invalid security group ID format: {sg_id}\n"
|
|
363
|
+
f"Security group ID is too short. Expected format: sg-xxxxxxxx"
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
network_config = NetworkConfiguration(
|
|
367
|
+
network_mode="VPC",
|
|
368
|
+
network_mode_config=NetworkModeConfig(subnets=vpc_subnets, security_groups=vpc_security_groups),
|
|
369
|
+
)
|
|
370
|
+
log.info("Network mode: VPC with %d subnets and %d security groups", len(vpc_subnets), len(vpc_security_groups))
|
|
371
|
+
else:
|
|
372
|
+
network_config = NetworkConfiguration(network_mode="PUBLIC")
|
|
373
|
+
log.info("Network mode: PUBLIC")
|
|
374
|
+
|
|
308
375
|
# Generate Dockerfile and .dockerignore
|
|
309
376
|
bedrock_agentcore_name = None
|
|
310
377
|
# Try to find the variable name for the Bedrock AgentCore instance in the file
|
|
@@ -332,6 +399,11 @@ def configure_bedrock_agentcore(
|
|
|
332
399
|
else:
|
|
333
400
|
dockerfile_output_dir = build_dir
|
|
334
401
|
|
|
402
|
+
if memory_config.mode == "NO_MEMORY":
|
|
403
|
+
memory_id = None
|
|
404
|
+
memory_name = None
|
|
405
|
+
log.debug("Cleared memory_id/name for Dockerfile generation (memory disabled)")
|
|
406
|
+
|
|
335
407
|
# Generate Dockerfile in the correct location (no moving needed)
|
|
336
408
|
dockerfile_path = runtime.generate_dockerfile(
|
|
337
409
|
entrypoint_path,
|
|
@@ -374,6 +446,32 @@ def configure_bedrock_agentcore(
|
|
|
374
446
|
log.debug("Agent name from BedrockAgentCoreApp: %s", agent_name)
|
|
375
447
|
log.debug("Config path: %s", config_path)
|
|
376
448
|
|
|
449
|
+
existing_project_config = load_config_if_exists(config_path)
|
|
450
|
+
|
|
451
|
+
if existing_project_config and agent_name in existing_project_config.agents:
|
|
452
|
+
existing_agent = existing_project_config.agents[agent_name]
|
|
453
|
+
existing_network = existing_agent.aws.network_configuration
|
|
454
|
+
|
|
455
|
+
# Import validation helper
|
|
456
|
+
from .vpc_validation import check_network_immutability
|
|
457
|
+
|
|
458
|
+
# Check if network config is being changed
|
|
459
|
+
error = check_network_immutability(
|
|
460
|
+
existing_network_mode=existing_network.network_mode,
|
|
461
|
+
existing_subnets=existing_network.network_mode_config.subnets
|
|
462
|
+
if existing_network.network_mode_config
|
|
463
|
+
else None,
|
|
464
|
+
existing_security_groups=existing_network.network_mode_config.security_groups
|
|
465
|
+
if existing_network.network_mode_config
|
|
466
|
+
else None,
|
|
467
|
+
new_network_mode="VPC" if vpc_enabled else "PUBLIC",
|
|
468
|
+
new_subnets=vpc_subnets,
|
|
469
|
+
new_security_groups=vpc_security_groups,
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
if error:
|
|
473
|
+
raise ValueError(error)
|
|
474
|
+
|
|
377
475
|
# Convert to POSIX for cross-platform compatibility
|
|
378
476
|
entrypoint_path_str = entrypoint_path.as_posix()
|
|
379
477
|
|
|
@@ -418,9 +516,10 @@ def configure_bedrock_agentcore(
|
|
|
418
516
|
region=region,
|
|
419
517
|
ecr_repository=ecr_repository,
|
|
420
518
|
ecr_auto_create=ecr_auto_create_value,
|
|
421
|
-
network_configuration=
|
|
519
|
+
network_configuration=network_config,
|
|
422
520
|
protocol_configuration=ProtocolConfiguration(server_protocol=protocol or "HTTP"),
|
|
423
521
|
observability=ObservabilityConfig(enabled=enable_observability),
|
|
522
|
+
lifecycle_configuration=lifecycle_config,
|
|
424
523
|
),
|
|
425
524
|
bedrock_agentcore=BedrockAgentCoreDeploymentInfo(),
|
|
426
525
|
codebuild=CodeBuildConfig(
|
|
@@ -438,6 +537,18 @@ def configure_bedrock_agentcore(
|
|
|
438
537
|
if verbose:
|
|
439
538
|
log.debug("Configuration saved with agent: %s", agent_name)
|
|
440
539
|
|
|
540
|
+
# Get VPC ID for display if VPC mode
|
|
541
|
+
vpc_id = None
|
|
542
|
+
if vpc_enabled and vpc_subnets:
|
|
543
|
+
try:
|
|
544
|
+
session = boto3.Session(region_name=region)
|
|
545
|
+
ec2_client = session.client("ec2", region_name=region)
|
|
546
|
+
subnet_response = ec2_client.describe_subnets(SubnetIds=[vpc_subnets[0]])
|
|
547
|
+
if subnet_response["Subnets"]:
|
|
548
|
+
vpc_id = subnet_response["Subnets"][0]["VpcId"]
|
|
549
|
+
except Exception:
|
|
550
|
+
pass # nosec B110
|
|
551
|
+
|
|
441
552
|
return ConfigureResult(
|
|
442
553
|
config_path=config_path,
|
|
443
554
|
dockerfile_path=dockerfile_path,
|
|
@@ -448,6 +559,10 @@ def configure_bedrock_agentcore(
|
|
|
448
559
|
execution_role=execution_role_arn,
|
|
449
560
|
ecr_repository=ecr_repository,
|
|
450
561
|
auto_create_ecr=auto_create_ecr and not ecr_repository,
|
|
562
|
+
network_mode="VPC" if vpc_enabled else "PUBLIC",
|
|
563
|
+
network_subnets=vpc_subnets if vpc_enabled else None,
|
|
564
|
+
network_security_groups=vpc_security_groups if vpc_enabled else None,
|
|
565
|
+
network_vpc_id=vpc_id,
|
|
451
566
|
)
|
|
452
567
|
|
|
453
568
|
|
|
@@ -31,59 +31,6 @@ def invoke_bedrock_agentcore(
|
|
|
31
31
|
project_config = load_config(config_path)
|
|
32
32
|
agent_config = project_config.get_agent_config(agent_name)
|
|
33
33
|
|
|
34
|
-
# Check memory status on first invoke if memory is enabled (STM or LTM)
|
|
35
|
-
if (
|
|
36
|
-
agent_config.memory
|
|
37
|
-
and agent_config.memory.mode != "NO_MEMORY"
|
|
38
|
-
and agent_config.memory.memory_id
|
|
39
|
-
and not agent_config.memory.first_invoke_memory_check_done
|
|
40
|
-
):
|
|
41
|
-
try:
|
|
42
|
-
from ...operations.memory.constants import MemoryStatus
|
|
43
|
-
from ...operations.memory.manager import MemoryManager
|
|
44
|
-
|
|
45
|
-
memory_manager = MemoryManager(region_name=agent_config.aws.region)
|
|
46
|
-
memory_status = memory_manager.get_memory_status(agent_config.memory.memory_id)
|
|
47
|
-
|
|
48
|
-
if memory_status != MemoryStatus.ACTIVE.value:
|
|
49
|
-
# Determine memory type for better messaging
|
|
50
|
-
memory_type = "Memory"
|
|
51
|
-
if agent_config.memory.has_ltm:
|
|
52
|
-
memory_type = "Long-term memory"
|
|
53
|
-
time_estimate = "60-180 seconds"
|
|
54
|
-
else:
|
|
55
|
-
memory_type = "Short-term memory"
|
|
56
|
-
time_estimate = "30-90 seconds"
|
|
57
|
-
|
|
58
|
-
# Provide graceful error message
|
|
59
|
-
error_message = (
|
|
60
|
-
f"Memory is still provisioning (current status: {memory_status}). "
|
|
61
|
-
f"{memory_type} takes {time_estimate} to activate.\n\n"
|
|
62
|
-
f"Please wait and check status with:\n"
|
|
63
|
-
f" agentcore status{f' --agent {agent_name}' if agent_name else ''}"
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
# Log the message for visibility
|
|
67
|
-
log.warning("Memory not yet active for agent '%s': %s", agent_config.name, memory_status)
|
|
68
|
-
|
|
69
|
-
raise ValueError(error_message)
|
|
70
|
-
|
|
71
|
-
# Memory is active, mark check as done
|
|
72
|
-
agent_config.memory.first_invoke_memory_check_done = True
|
|
73
|
-
project_config.agents[agent_config.name] = agent_config
|
|
74
|
-
save_config(project_config, config_path)
|
|
75
|
-
log.info("Memory is active, proceeding with invoke")
|
|
76
|
-
|
|
77
|
-
except ImportError as e:
|
|
78
|
-
log.error("Failed to import MemoryManager: %s", e)
|
|
79
|
-
# Continue without check if import fails
|
|
80
|
-
except Exception as e:
|
|
81
|
-
# If it's our ValueError, re-raise it
|
|
82
|
-
if "Memory is still provisioning" in str(e):
|
|
83
|
-
raise
|
|
84
|
-
# For other errors, log but continue
|
|
85
|
-
log.warning("Could not check memory status: %s", e)
|
|
86
|
-
|
|
87
34
|
# Log which agent is being invoked
|
|
88
35
|
mode = "locally" if local_mode else "via cloud endpoint"
|
|
89
36
|
log.debug("Invoking BedrockAgentCore agent '%s' %s", agent_config.name, mode)
|
|
@@ -5,10 +5,11 @@ import logging
|
|
|
5
5
|
import time
|
|
6
6
|
import urllib.parse
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import Optional
|
|
8
|
+
from typing import List, Optional
|
|
9
9
|
|
|
10
10
|
import boto3
|
|
11
11
|
from botocore.exceptions import ClientError
|
|
12
|
+
from rich.console import Console
|
|
12
13
|
|
|
13
14
|
from ...services.codebuild import CodeBuildService
|
|
14
15
|
from ...services.ecr import deploy_to_ecr, get_or_create_ecr_repository
|
|
@@ -22,9 +23,122 @@ from .create_role import get_or_create_runtime_execution_role
|
|
|
22
23
|
from .exceptions import RuntimeToolkitException
|
|
23
24
|
from .models import LaunchResult
|
|
24
25
|
|
|
26
|
+
# console = Console()
|
|
27
|
+
|
|
25
28
|
log = logging.getLogger(__name__)
|
|
26
29
|
|
|
27
30
|
|
|
31
|
+
def _validate_vpc_resources(session: boto3.Session, agent_config, region: str) -> None:
|
|
32
|
+
"""Validate VPC resources exist and are in the same VPC.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
session: Boto3 session
|
|
36
|
+
agent_config: Agent configuration
|
|
37
|
+
region: AWS region
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
ValueError: If validation fails
|
|
41
|
+
"""
|
|
42
|
+
network_config = agent_config.aws.network_configuration
|
|
43
|
+
|
|
44
|
+
if network_config.network_mode != "VPC":
|
|
45
|
+
return # Nothing to validate for PUBLIC mode
|
|
46
|
+
|
|
47
|
+
if not network_config.network_mode_config:
|
|
48
|
+
raise ValueError("VPC mode requires network configuration")
|
|
49
|
+
|
|
50
|
+
subnets = network_config.network_mode_config.subnets
|
|
51
|
+
security_groups = network_config.network_mode_config.security_groups
|
|
52
|
+
|
|
53
|
+
if not subnets or not security_groups:
|
|
54
|
+
raise ValueError("VPC mode requires both subnets and security groups")
|
|
55
|
+
|
|
56
|
+
ec2_client = session.client("ec2", region_name=region)
|
|
57
|
+
|
|
58
|
+
# Validate subnets exist and get their VPC IDs
|
|
59
|
+
try:
|
|
60
|
+
subnet_response = ec2_client.describe_subnets(SubnetIds=subnets)
|
|
61
|
+
subnet_vpcs = {subnet["VpcId"] for subnet in subnet_response["Subnets"]}
|
|
62
|
+
|
|
63
|
+
if len(subnet_vpcs) > 1:
|
|
64
|
+
raise ValueError(
|
|
65
|
+
f"All subnets must be in the same VPC. "
|
|
66
|
+
f"Found subnets in {len(subnet_vpcs)} different VPCs: {subnet_vpcs}"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
vpc_id = subnet_vpcs.pop()
|
|
70
|
+
log.info("✓ All %d subnets are in VPC: %s", len(subnets), vpc_id)
|
|
71
|
+
|
|
72
|
+
except ClientError as e:
|
|
73
|
+
if e.response["Error"]["Code"] == "InvalidSubnetID.NotFound":
|
|
74
|
+
raise ValueError(f"One or more subnet IDs not found: {subnets}") from e
|
|
75
|
+
raise ValueError(f"Failed to validate subnets: {e}") from e
|
|
76
|
+
|
|
77
|
+
# Validate security groups exist and are in the same VPC
|
|
78
|
+
try:
|
|
79
|
+
sg_response = ec2_client.describe_security_groups(GroupIds=security_groups)
|
|
80
|
+
sg_vpcs = {sg["VpcId"] for sg in sg_response["SecurityGroups"]}
|
|
81
|
+
|
|
82
|
+
if len(sg_vpcs) > 1:
|
|
83
|
+
raise ValueError(
|
|
84
|
+
f"All security groups must be in the same VPC. Found {len(sg_vpcs)} different VPCs: {sg_vpcs}"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
sg_vpc_id = sg_vpcs.pop()
|
|
88
|
+
|
|
89
|
+
if sg_vpc_id != vpc_id:
|
|
90
|
+
raise ValueError(
|
|
91
|
+
f"Security groups must be in the same VPC as subnets. "
|
|
92
|
+
f"Subnets are in VPC {vpc_id}, but security groups are in VPC {sg_vpc_id}"
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
log.info("✓ All %d security groups are in VPC: %s", len(security_groups), vpc_id)
|
|
96
|
+
|
|
97
|
+
except ClientError as e:
|
|
98
|
+
if e.response["Error"]["Code"] == "InvalidGroup.NotFound":
|
|
99
|
+
raise ValueError(f"One or more security group IDs not found: {security_groups}") from e
|
|
100
|
+
raise ValueError(f"Failed to validate security groups: {e}") from e
|
|
101
|
+
|
|
102
|
+
log.info("✓ VPC configuration validated successfully")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _ensure_network_service_linked_role(session: boto3.Session, logger) -> None:
|
|
106
|
+
"""Ensure the AgentCore Network service-linked role exists."""
|
|
107
|
+
iam_client = session.client("iam")
|
|
108
|
+
role_name = "AWSServiceRoleForBedrockAgentCoreNetwork"
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
# Check if role exists
|
|
112
|
+
iam_client.get_role(RoleName=role_name)
|
|
113
|
+
logger.info("✓ VPC service-linked role verified: %s", role_name)
|
|
114
|
+
|
|
115
|
+
except ClientError as e:
|
|
116
|
+
if e.response["Error"]["Code"] != "NoSuchEntity":
|
|
117
|
+
raise
|
|
118
|
+
|
|
119
|
+
logger.info("Creating VPC service-linked role...")
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
iam_client.create_service_linked_role(
|
|
123
|
+
AWSServiceName="network.bedrock-agentcore.amazonaws.com",
|
|
124
|
+
Description="Service-linked role for Amazon Bedrock AgentCore VPC networking",
|
|
125
|
+
)
|
|
126
|
+
logger.info("✓ VPC service-linked role created: %s", role_name)
|
|
127
|
+
|
|
128
|
+
# Wait for propagation
|
|
129
|
+
import time
|
|
130
|
+
|
|
131
|
+
logger.info(" Waiting 10 seconds for IAM propagation...")
|
|
132
|
+
time.sleep(10)
|
|
133
|
+
|
|
134
|
+
except ClientError as e:
|
|
135
|
+
if e.response["Error"]["Code"] == "InvalidInput":
|
|
136
|
+
logger.info("✓ VPC service-linked role verified (created by another process)")
|
|
137
|
+
else:
|
|
138
|
+
logger.error("✗ Failed to create service-linked role: %s", e)
|
|
139
|
+
raise
|
|
140
|
+
|
|
141
|
+
|
|
28
142
|
def _ensure_ecr_repository(agent_config, project_config, config_path, agent_name, region):
|
|
29
143
|
"""Ensure ECR repository exists (idempotent)."""
|
|
30
144
|
ecr_uri = agent_config.aws.ecr_repository
|
|
@@ -136,6 +250,7 @@ def _ensure_memory_for_agent(
|
|
|
136
250
|
project_config: BedrockAgentCoreConfigSchema,
|
|
137
251
|
config_path: Path,
|
|
138
252
|
agent_name: str,
|
|
253
|
+
console: Optional[Console] = None,
|
|
139
254
|
) -> Optional[str]:
|
|
140
255
|
"""Ensure memory resource exists for agent. Returns memory_id or None.
|
|
141
256
|
|
|
@@ -161,7 +276,10 @@ def _ensure_memory_for_agent(
|
|
|
161
276
|
from ...operations.memory.constants import StrategyType
|
|
162
277
|
from ...operations.memory.manager import MemoryManager
|
|
163
278
|
|
|
164
|
-
memory_manager = MemoryManager(
|
|
279
|
+
memory_manager = MemoryManager(
|
|
280
|
+
region_name=agent_config.aws.region,
|
|
281
|
+
console=console, # ADD THIS
|
|
282
|
+
)
|
|
165
283
|
memory_name = f"{agent_name}_mem" # Short name under 48 char limit
|
|
166
284
|
|
|
167
285
|
# Check if memory already exists in cloud
|
|
@@ -190,6 +308,7 @@ def _ensure_memory_for_agent(
|
|
|
190
308
|
# If LTM is enabled but no strategies exist, add them
|
|
191
309
|
if agent_config.memory.has_ltm and len(existing_strategies) == 0:
|
|
192
310
|
log.info("Adding LTM strategies to existing memory...")
|
|
311
|
+
console.print("⏳ Adding long-term memory strategies (this may take 30-180 seconds)...")
|
|
193
312
|
memory_manager.update_memory_strategies_and_wait(
|
|
194
313
|
memory_id=existing_memory.id,
|
|
195
314
|
add_strategies=[
|
|
@@ -212,13 +331,21 @@ def _ensure_memory_for_agent(
|
|
|
212
331
|
}
|
|
213
332
|
},
|
|
214
333
|
],
|
|
215
|
-
max_wait=30
|
|
334
|
+
max_wait=300, # CHANGE: Increased from 30 to 300
|
|
216
335
|
poll_interval=5,
|
|
217
336
|
)
|
|
218
337
|
memory = existing_memory
|
|
219
338
|
log.info("✅ LTM strategies added to existing memory")
|
|
220
339
|
else:
|
|
221
|
-
memory
|
|
340
|
+
# CHANGE: ADD THIS BLOCK - Wait for existing memory to become ACTIVE
|
|
341
|
+
console.print("⏳ Waiting for existing memory to become ACTIVE...")
|
|
342
|
+
memory = memory_manager._wait_for_memory_active(
|
|
343
|
+
existing_memory.id,
|
|
344
|
+
max_wait=300,
|
|
345
|
+
poll_interval=5,
|
|
346
|
+
)
|
|
347
|
+
# END CHANGE
|
|
348
|
+
|
|
222
349
|
if agent_config.memory.has_ltm and len(existing_strategies) > 0:
|
|
223
350
|
log.info("✅ Using existing memory with %d strategies", len(existing_strategies))
|
|
224
351
|
else:
|
|
@@ -251,28 +378,29 @@ def _ensure_memory_for_agent(
|
|
|
251
378
|
else:
|
|
252
379
|
log.info("Creating new STM-only memory...")
|
|
253
380
|
|
|
254
|
-
# Use
|
|
255
|
-
memory
|
|
381
|
+
# CHANGE: Use create_memory_and_wait instead of _create_memory
|
|
382
|
+
console.print("⏳ Creating memory resource (this may take 30-180 seconds)...")
|
|
383
|
+
memory = memory_manager.create_memory_and_wait(
|
|
256
384
|
name=memory_name,
|
|
257
385
|
description=f"Memory for agent {agent_name} with {'STM+LTM' if strategies else 'STM only'}",
|
|
258
386
|
strategies=strategies,
|
|
259
387
|
event_expiry_days=agent_config.memory.event_expiry_days or 30,
|
|
260
|
-
|
|
388
|
+
max_wait=300, # 5 minutes
|
|
389
|
+
poll_interval=5,
|
|
261
390
|
)
|
|
391
|
+
log.info("✅ Memory created and active: %s", memory.id)
|
|
392
|
+
# END CHANGE
|
|
262
393
|
|
|
263
|
-
#
|
|
264
|
-
# (Should already be True from configure if user chose CREATE_NEW)
|
|
394
|
+
# CHANGE: ADD THIS - Mark as created by toolkit since we just created it
|
|
265
395
|
if not agent_config.memory.was_created_by_toolkit:
|
|
266
|
-
log.warning("Memory created but flag was False - correcting to True")
|
|
267
396
|
agent_config.memory.was_created_by_toolkit = True
|
|
268
|
-
|
|
269
|
-
log.info("✅ New memory created: %s (provisioning in background)", memory.id)
|
|
397
|
+
# END CHANGE
|
|
270
398
|
|
|
271
399
|
# Save memory configuration (preserving was_created_by_toolkit flag)
|
|
272
400
|
agent_config.memory.memory_id = memory.id
|
|
273
401
|
agent_config.memory.memory_arn = memory.arn
|
|
274
402
|
agent_config.memory.memory_name = memory_name
|
|
275
|
-
agent_config.memory.first_invoke_memory_check_done =
|
|
403
|
+
agent_config.memory.first_invoke_memory_check_done = True # CHANGE: Set to True since memory is now ACTIVE
|
|
276
404
|
|
|
277
405
|
project_config.agents[agent_config.name] = agent_config
|
|
278
406
|
save_config(project_config, config_path)
|
|
@@ -303,8 +431,8 @@ def _deploy_to_bedrock_agentcore(
|
|
|
303
431
|
if env_vars is None:
|
|
304
432
|
env_vars = {}
|
|
305
433
|
|
|
306
|
-
# Add memory configuration to env_vars if
|
|
307
|
-
if agent_config.memory and agent_config.memory.memory_id:
|
|
434
|
+
# Add memory configuration to env_vars only if memory is enabled
|
|
435
|
+
if agent_config.memory and agent_config.memory.mode != "NO_MEMORY" and agent_config.memory.memory_id:
|
|
308
436
|
env_vars["BEDROCK_AGENTCORE_MEMORY_ID"] = agent_config.memory.memory_id
|
|
309
437
|
env_vars["BEDROCK_AGENTCORE_MEMORY_NAME"] = agent_config.memory.memory_name
|
|
310
438
|
log.info("Passing memory configuration to agent: %s", agent_config.memory.memory_id)
|
|
@@ -315,6 +443,15 @@ def _deploy_to_bedrock_agentcore(
|
|
|
315
443
|
network_config = agent_config.aws.network_configuration.to_aws_dict()
|
|
316
444
|
protocol_config = agent_config.aws.protocol_configuration.to_aws_dict()
|
|
317
445
|
|
|
446
|
+
lifecycle_config = None
|
|
447
|
+
if agent_config.aws.lifecycle_configuration.has_custom_settings:
|
|
448
|
+
lifecycle_config = agent_config.aws.lifecycle_configuration.to_aws_dict()
|
|
449
|
+
log.info(
|
|
450
|
+
"Applying custom lifecycle settings: idle=%s, max=%s",
|
|
451
|
+
agent_config.aws.lifecycle_configuration.idle_runtime_session_timeout,
|
|
452
|
+
agent_config.aws.lifecycle_configuration.max_lifetime,
|
|
453
|
+
)
|
|
454
|
+
|
|
318
455
|
# Execution role should be available by now (either provided or auto-created)
|
|
319
456
|
if not agent_config.aws.execution_role:
|
|
320
457
|
raise ValueError(
|
|
@@ -340,6 +477,7 @@ def _deploy_to_bedrock_agentcore(
|
|
|
340
477
|
protocol_config=protocol_config,
|
|
341
478
|
env_vars=env_vars,
|
|
342
479
|
auto_update_on_conflict=auto_update_on_conflict,
|
|
480
|
+
lifecycle_config=lifecycle_config,
|
|
343
481
|
)
|
|
344
482
|
break # Success! Exit retry loop
|
|
345
483
|
|
|
@@ -414,9 +552,45 @@ def _deploy_to_bedrock_agentcore(
|
|
|
414
552
|
result = bedrock_agentcore_client.wait_for_agent_endpoint_ready(agent_id)
|
|
415
553
|
log.info("Agent endpoint: %s", result)
|
|
416
554
|
|
|
555
|
+
if agent_config.aws.network_configuration.network_mode == "VPC":
|
|
556
|
+
vpc_subnets = agent_config.aws.network_configuration.network_mode_config.subnets
|
|
557
|
+
session = boto3.Session(region_name=region)
|
|
558
|
+
_check_vpc_deployment(session, agent_id, vpc_subnets, region)
|
|
559
|
+
|
|
417
560
|
return agent_id, agent_arn
|
|
418
561
|
|
|
419
562
|
|
|
563
|
+
def _check_vpc_deployment(session: boto3.Session, agent_id: str, vpc_subnets: List[str], region: str) -> None:
|
|
564
|
+
"""Verify VPC deployment created ENIs in the specified subnets."""
|
|
565
|
+
ec2_client = session.client("ec2", region_name=region)
|
|
566
|
+
|
|
567
|
+
try:
|
|
568
|
+
# Look for ENIs in our subnets with AgentCore description
|
|
569
|
+
response = ec2_client.describe_network_interfaces(
|
|
570
|
+
Filters=[
|
|
571
|
+
{"Name": "subnet-id", "Values": vpc_subnets},
|
|
572
|
+
{"Name": "description", "Values": ["*AgentCore*", "*bedrock-agentcore*"]},
|
|
573
|
+
]
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
all_enis = response.get("NetworkInterfaces", [])
|
|
577
|
+
our_enis = [eni for eni in all_enis if eni.get("SubnetId") in vpc_subnets]
|
|
578
|
+
|
|
579
|
+
if our_enis:
|
|
580
|
+
log.info("✓ Found %d ENI(s) in configured subnets:", len(our_enis))
|
|
581
|
+
for eni in our_enis:
|
|
582
|
+
log.info(" - ENI ID: %s", eni["NetworkInterfaceId"])
|
|
583
|
+
log.info(" Subnet: %s", eni["SubnetId"])
|
|
584
|
+
log.info(" Private IP: %s", eni.get("PrivateIpAddress", "N/A"))
|
|
585
|
+
log.info(" Status: %s", eni["Status"])
|
|
586
|
+
log.info(" Security Groups: %s", [sg["GroupId"] for sg in eni.get("Groups", [])])
|
|
587
|
+
else:
|
|
588
|
+
log.info(":information_source: VPC network interfaces will be created on first invocation")
|
|
589
|
+
|
|
590
|
+
except Exception as e:
|
|
591
|
+
log.error("Error checking ENIs: %s", e)
|
|
592
|
+
|
|
593
|
+
|
|
420
594
|
def launch_bedrock_agentcore(
|
|
421
595
|
config_path: Path,
|
|
422
596
|
agent_name: Optional[str] = None,
|
|
@@ -424,6 +598,7 @@ def launch_bedrock_agentcore(
|
|
|
424
598
|
use_codebuild: bool = True,
|
|
425
599
|
env_vars: Optional[dict] = None,
|
|
426
600
|
auto_update_on_conflict: bool = False,
|
|
601
|
+
console: Optional[Console] = None,
|
|
427
602
|
) -> LaunchResult:
|
|
428
603
|
"""Launch Bedrock AgentCore locally or to cloud.
|
|
429
604
|
|
|
@@ -434,10 +609,14 @@ def launch_bedrock_agentcore(
|
|
|
434
609
|
use_codebuild: Whether to use CodeBuild for ARM64 builds
|
|
435
610
|
env_vars: Environment variables to pass to local container (dict of key-value pairs)
|
|
436
611
|
auto_update_on_conflict: Whether to automatically update when agent already exists (default: False)
|
|
612
|
+
console: Optional Rich Console instance for progress output. Used to maintain
|
|
613
|
+
output hierarchy with CLI status contexts.
|
|
437
614
|
|
|
438
615
|
Returns:
|
|
439
616
|
LaunchResult model with launch details
|
|
440
617
|
"""
|
|
618
|
+
if console is None:
|
|
619
|
+
console = Console()
|
|
441
620
|
# Load project configuration
|
|
442
621
|
project_config = load_config(config_path)
|
|
443
622
|
agent_config = project_config.get_agent_config(agent_name)
|
|
@@ -445,10 +624,25 @@ def launch_bedrock_agentcore(
|
|
|
445
624
|
if env_vars is None:
|
|
446
625
|
env_vars = {}
|
|
447
626
|
|
|
627
|
+
if agent_config.aws.network_configuration.network_mode == "VPC":
|
|
628
|
+
if local:
|
|
629
|
+
log.warning("⚠️ VPC configuration detected but running in local mode. VPC settings will be ignored.")
|
|
630
|
+
else:
|
|
631
|
+
log.info("Validating VPC resources...")
|
|
632
|
+
session = boto3.Session(region_name=agent_config.aws.region)
|
|
633
|
+
_validate_vpc_resources(session, agent_config, agent_config.aws.region)
|
|
634
|
+
|
|
635
|
+
# Ensure service-linked role exists for VPC networking
|
|
636
|
+
_ensure_network_service_linked_role(session, log)
|
|
637
|
+
|
|
448
638
|
# Ensure memory exists for non-CodeBuild paths
|
|
449
639
|
if not use_codebuild:
|
|
450
640
|
_ensure_memory_for_agent(agent_config, project_config, config_path, agent_config.name)
|
|
451
641
|
|
|
642
|
+
# Ensure memory exists for non-CodeBuild paths
|
|
643
|
+
if not use_codebuild:
|
|
644
|
+
_ensure_memory_for_agent(agent_config, project_config, config_path, agent_config.name, console=console)
|
|
645
|
+
|
|
452
646
|
# Add memory configuration to environment variables if available
|
|
453
647
|
if agent_config.memory and agent_config.memory.memory_id:
|
|
454
648
|
env_vars["BEDROCK_AGENTCORE_MEMORY_ID"] = agent_config.memory.memory_id
|
|
@@ -705,10 +899,13 @@ def _launch_with_codebuild(
|
|
|
705
899
|
project_config,
|
|
706
900
|
auto_update_on_conflict: bool = False,
|
|
707
901
|
env_vars: Optional[dict] = None,
|
|
902
|
+
console: Optional[Console] = None,
|
|
708
903
|
) -> LaunchResult:
|
|
709
904
|
"""Launch using CodeBuild for ARM64 builds."""
|
|
905
|
+
if console is None:
|
|
906
|
+
console = Console()
|
|
710
907
|
# Create memory if configured
|
|
711
|
-
_ensure_memory_for_agent(agent_config, project_config, config_path, agent_name)
|
|
908
|
+
_ensure_memory_for_agent(agent_config, project_config, config_path, agent_name, console=console)
|
|
712
909
|
|
|
713
910
|
# Execute shared CodeBuild workflow with full deployment mode
|
|
714
911
|
build_id, ecr_uri, region, account_id = _execute_codebuild_workflow(
|