bedrock-agentcore-starter-toolkit 0.1.21__py3-none-any.whl → 0.1.23__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/common.py +1 -1
- bedrock_agentcore_starter_toolkit/cli/runtime/commands.py +167 -51
- bedrock_agentcore_starter_toolkit/cli/runtime/configuration_manager.py +45 -17
- bedrock_agentcore_starter_toolkit/notebook/runtime/bedrock_agentcore.py +30 -1
- bedrock_agentcore_starter_toolkit/operations/memory/manager.py +1 -1
- bedrock_agentcore_starter_toolkit/operations/runtime/__init__.py +12 -1
- bedrock_agentcore_starter_toolkit/operations/runtime/configure.py +182 -29
- bedrock_agentcore_starter_toolkit/operations/runtime/destroy.py +24 -7
- bedrock_agentcore_starter_toolkit/operations/runtime/exceptions.py +27 -0
- bedrock_agentcore_starter_toolkit/operations/runtime/invoke.py +12 -3
- bedrock_agentcore_starter_toolkit/operations/runtime/launch.py +99 -44
- bedrock_agentcore_starter_toolkit/operations/runtime/status.py +5 -4
- bedrock_agentcore_starter_toolkit/services/codebuild.py +53 -26
- bedrock_agentcore_starter_toolkit/services/ecr.py +44 -2
- bedrock_agentcore_starter_toolkit/utils/runtime/config.py +43 -1
- bedrock_agentcore_starter_toolkit/utils/runtime/container.py +89 -30
- bedrock_agentcore_starter_toolkit/utils/runtime/schema.py +43 -4
- bedrock_agentcore_starter_toolkit/utils/runtime/templates/Dockerfile.j2 +1 -6
- bedrock_agentcore_starter_toolkit/utils/runtime/templates/dockerignore.template +1 -0
- {bedrock_agentcore_starter_toolkit-0.1.21.dist-info → bedrock_agentcore_starter_toolkit-0.1.23.dist-info}/METADATA +2 -2
- {bedrock_agentcore_starter_toolkit-0.1.21.dist-info → bedrock_agentcore_starter_toolkit-0.1.23.dist-info}/RECORD +25 -24
- {bedrock_agentcore_starter_toolkit-0.1.21.dist-info → bedrock_agentcore_starter_toolkit-0.1.23.dist-info}/WHEEL +0 -0
- {bedrock_agentcore_starter_toolkit-0.1.21.dist-info → bedrock_agentcore_starter_toolkit-0.1.23.dist-info}/entry_points.txt +0 -0
- {bedrock_agentcore_starter_toolkit-0.1.21.dist-info → bedrock_agentcore_starter_toolkit-0.1.23.dist-info}/licenses/LICENSE.txt +0 -0
- {bedrock_agentcore_starter_toolkit-0.1.21.dist-info → bedrock_agentcore_starter_toolkit-0.1.23.dist-info}/licenses/NOTICE.txt +0 -0
|
@@ -38,7 +38,11 @@ def get_status(config_path: Path, agent_name: Optional[str] = None) -> StatusRes
|
|
|
38
38
|
agent_arn=agent_config.bedrock_agentcore.agent_arn,
|
|
39
39
|
)
|
|
40
40
|
|
|
41
|
-
if
|
|
41
|
+
# Check if memory is disabled first
|
|
42
|
+
if agent_config.memory and agent_config.memory.mode == "NO_MEMORY":
|
|
43
|
+
config_info.memory_type = "Disabled"
|
|
44
|
+
config_info.memory_enabled = False
|
|
45
|
+
elif agent_config.memory and agent_config.memory.memory_id:
|
|
42
46
|
try:
|
|
43
47
|
from ...operations.memory.manager import MemoryManager
|
|
44
48
|
|
|
@@ -91,9 +95,6 @@ def get_status(config_path: Path, agent_name: Optional[str] = None) -> StatusRes
|
|
|
91
95
|
|
|
92
96
|
config_info.memory_id = agent_config.memory.memory_id
|
|
93
97
|
config_info.memory_status = memory_status
|
|
94
|
-
|
|
95
|
-
# Add the detailed memory info to config_info
|
|
96
|
-
# You'll need to add this field to StatusConfigInfo model
|
|
97
98
|
config_info.memory_details = memory_details
|
|
98
99
|
|
|
99
100
|
except Exception as e:
|
|
@@ -6,13 +6,15 @@ import os
|
|
|
6
6
|
import tempfile
|
|
7
7
|
import time
|
|
8
8
|
import zipfile
|
|
9
|
+
from importlib.resources import files
|
|
9
10
|
from pathlib import Path
|
|
10
|
-
from typing import List
|
|
11
|
+
from typing import List, Optional
|
|
11
12
|
|
|
12
13
|
import boto3
|
|
13
14
|
from botocore.exceptions import ClientError
|
|
14
15
|
|
|
15
16
|
from ..operations.runtime.create_role import get_or_create_codebuild_execution_role
|
|
17
|
+
from .ecr import sanitize_ecr_repo_name
|
|
16
18
|
|
|
17
19
|
|
|
18
20
|
class CodeBuildService:
|
|
@@ -70,21 +72,28 @@ class CodeBuildService:
|
|
|
70
72
|
|
|
71
73
|
return bucket_name
|
|
72
74
|
|
|
73
|
-
def upload_source(self, agent_name: str) -> str:
|
|
74
|
-
"""Upload
|
|
75
|
+
def upload_source(self, agent_name: str, source_dir: str = ".", dockerfile_dir: Optional[str] = None) -> str:
|
|
76
|
+
"""Upload source directory to S3, respecting .dockerignore patterns.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
agent_name: Name of the agent
|
|
80
|
+
source_dir: Directory to upload (defaults to current directory)
|
|
81
|
+
dockerfile_dir: Directory containing Dockerfile (may be different from source_dir)
|
|
82
|
+
"""
|
|
75
83
|
account_id = self.account_id
|
|
76
84
|
bucket_name = self.ensure_source_bucket(account_id)
|
|
77
85
|
self.source_bucket = bucket_name
|
|
78
86
|
|
|
79
|
-
# Parse .dockerignore patterns
|
|
87
|
+
# Parse .dockerignore patterns from template for consistent filtering
|
|
80
88
|
ignore_patterns = self._parse_dockerignore()
|
|
81
89
|
|
|
82
90
|
with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as temp_zip:
|
|
83
91
|
try:
|
|
84
92
|
with zipfile.ZipFile(temp_zip.name, "w", zipfile.ZIP_DEFLATED) as zipf:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
93
|
+
# First, add all files from source_dir
|
|
94
|
+
for root, dirs, files in os.walk(source_dir):
|
|
95
|
+
# Convert to relative path from source_dir
|
|
96
|
+
rel_root = os.path.relpath(root, source_dir)
|
|
88
97
|
if rel_root == ".":
|
|
89
98
|
rel_root = ""
|
|
90
99
|
|
|
@@ -107,6 +116,16 @@ class CodeBuildService:
|
|
|
107
116
|
file_path = Path(root) / file
|
|
108
117
|
zipf.write(file_path, file_rel_path)
|
|
109
118
|
|
|
119
|
+
# If Dockerfile is in a different directory, include it in the zip
|
|
120
|
+
if dockerfile_dir and source_dir != dockerfile_dir:
|
|
121
|
+
dockerfile_path = Path(dockerfile_dir) / "Dockerfile"
|
|
122
|
+
source_dockerfile = Path(source_dir) / "Dockerfile"
|
|
123
|
+
|
|
124
|
+
if dockerfile_path.exists() and not source_dockerfile.exists():
|
|
125
|
+
# Include the Dockerfile from dockerfile_dir
|
|
126
|
+
zipf.write(dockerfile_path, "Dockerfile")
|
|
127
|
+
self.logger.info("Including Dockerfile from %s in source.zip", dockerfile_dir)
|
|
128
|
+
|
|
110
129
|
# Create agent-organized S3 key: agentname/source.zip (fixed naming for cache consistency)
|
|
111
130
|
s3_key = f"{agent_name}/source.zip"
|
|
112
131
|
|
|
@@ -141,7 +160,7 @@ class CodeBuildService:
|
|
|
141
160
|
self, agent_name: str, ecr_repository_uri: str, execution_role: str, source_location: str
|
|
142
161
|
) -> str:
|
|
143
162
|
"""Create or update CodeBuild project for ARM64 builds."""
|
|
144
|
-
project_name = f"bedrock-agentcore-{agent_name}-builder"
|
|
163
|
+
project_name = f"bedrock-agentcore-{sanitize_ecr_repo_name(agent_name)}-builder"
|
|
145
164
|
|
|
146
165
|
buildspec = self._get_arm64_buildspec(ecr_repository_uri)
|
|
147
166
|
|
|
@@ -280,21 +299,32 @@ phases:
|
|
|
280
299
|
"""
|
|
281
300
|
|
|
282
301
|
def _parse_dockerignore(self) -> List[str]:
|
|
283
|
-
"""Parse .dockerignore
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
302
|
+
"""Parse .dockerignore patterns from template for consistent filtering.
|
|
303
|
+
|
|
304
|
+
Always uses the dockerignore.template to ensure consistent file filtering
|
|
305
|
+
during zip creation, regardless of source_path configuration.
|
|
306
|
+
"""
|
|
307
|
+
# Use dockerignore.template from package resources
|
|
308
|
+
try:
|
|
309
|
+
template_content = (
|
|
310
|
+
files("bedrock_agentcore_starter_toolkit")
|
|
311
|
+
.joinpath("utils/runtime/templates/dockerignore.template")
|
|
312
|
+
.read_text()
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
patterns = []
|
|
316
|
+
for line in template_content.splitlines():
|
|
317
|
+
line = line.strip()
|
|
318
|
+
if line and not line.startswith("#"):
|
|
319
|
+
patterns.append(line)
|
|
320
|
+
|
|
321
|
+
self.logger.info("Using dockerignore.template with %d patterns for zip filtering", len(patterns))
|
|
322
|
+
return patterns
|
|
323
|
+
|
|
324
|
+
except Exception as e:
|
|
325
|
+
# Fallback to minimal default patterns if template not found
|
|
326
|
+
self.logger.warning("Could not load dockerignore.template (%s), using minimal default patterns", e)
|
|
327
|
+
return [
|
|
298
328
|
".git",
|
|
299
329
|
"__pycache__",
|
|
300
330
|
"*.pyc",
|
|
@@ -305,9 +335,6 @@ phases:
|
|
|
305
335
|
"*.egg-info",
|
|
306
336
|
".bedrock_agentcore.yaml", # Always exclude config
|
|
307
337
|
]
|
|
308
|
-
self.logger.info("No .dockerignore found, using default exclude patterns")
|
|
309
|
-
|
|
310
|
-
return patterns
|
|
311
338
|
|
|
312
339
|
def _should_ignore(self, path: str, patterns: List[str], is_dir: bool = False) -> bool:
|
|
313
340
|
"""Check if path should be ignored based on dockerignore patterns."""
|
|
@@ -1,12 +1,54 @@
|
|
|
1
1
|
"""ECR (Elastic Container Registry) service integration."""
|
|
2
2
|
|
|
3
3
|
import base64
|
|
4
|
+
import re
|
|
4
5
|
|
|
5
6
|
import boto3
|
|
6
7
|
|
|
7
8
|
from ..utils.runtime.container import ContainerRuntime
|
|
8
9
|
|
|
9
10
|
|
|
11
|
+
def sanitize_ecr_repo_name(name: str) -> str:
|
|
12
|
+
"""Sanitize agent name for ECR repository naming requirements.
|
|
13
|
+
|
|
14
|
+
ECR repository names must:
|
|
15
|
+
- Contain only lowercase letters, numbers, hyphens (-), underscores (_), and forward slashes (/)
|
|
16
|
+
- Start with a lowercase letter or number
|
|
17
|
+
- Be between 2 and 256 characters
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
name: Agent name to sanitize
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
Sanitized repository name component
|
|
24
|
+
"""
|
|
25
|
+
# Convert to lowercase
|
|
26
|
+
name = name.lower()
|
|
27
|
+
|
|
28
|
+
# Replace invalid characters with hyphens
|
|
29
|
+
name = re.sub(r"[^a-z0-9_\-/]", "-", name)
|
|
30
|
+
|
|
31
|
+
# Ensure starts with alphanumeric
|
|
32
|
+
if name and not name[0].isalnum():
|
|
33
|
+
name = "a" + name # Prefix with 'a' if starts with non-alphanumeric
|
|
34
|
+
|
|
35
|
+
# Remove consecutive hyphens/underscores
|
|
36
|
+
name = re.sub(r"[-_]{2,}", "-", name)
|
|
37
|
+
|
|
38
|
+
# Strip trailing hyphens/underscores
|
|
39
|
+
name = name.rstrip("-_")
|
|
40
|
+
|
|
41
|
+
# Ensure minimum length
|
|
42
|
+
if len(name) < 2:
|
|
43
|
+
name = name + "-agent"
|
|
44
|
+
|
|
45
|
+
# Truncate if too long (leave room for prefix)
|
|
46
|
+
if len(name) > 200:
|
|
47
|
+
name = name[:200].rstrip("-_")
|
|
48
|
+
|
|
49
|
+
return name
|
|
50
|
+
|
|
51
|
+
|
|
10
52
|
def get_account_id() -> str:
|
|
11
53
|
"""Get AWS account ID."""
|
|
12
54
|
return boto3.client("sts").get_caller_identity()["Account"]
|
|
@@ -38,8 +80,8 @@ def get_or_create_ecr_repository(agent_name: str, region: str) -> str:
|
|
|
38
80
|
Returns:
|
|
39
81
|
ECR repository URI
|
|
40
82
|
"""
|
|
41
|
-
# Generate deterministic repository name based on agent name
|
|
42
|
-
repo_name = f"bedrock-agentcore-{agent_name}"
|
|
83
|
+
# Generate deterministic repository name based on agent name (sanitized for ECR requirements)
|
|
84
|
+
repo_name = f"bedrock-agentcore-{sanitize_ecr_repo_name(agent_name)}"
|
|
43
85
|
|
|
44
86
|
ecr = boto3.client("ecr", region_name=region)
|
|
45
87
|
|
|
@@ -5,7 +5,9 @@ from pathlib import Path
|
|
|
5
5
|
from typing import Optional
|
|
6
6
|
|
|
7
7
|
import yaml
|
|
8
|
+
from pydantic import ValidationError
|
|
8
9
|
|
|
10
|
+
from ...operations.runtime.exceptions import RuntimeToolkitException
|
|
9
11
|
from .schema import BedrockAgentCoreAgentSchema, BedrockAgentCoreConfigSchema
|
|
10
12
|
|
|
11
13
|
log = logging.getLogger(__name__)
|
|
@@ -58,8 +60,25 @@ def load_config(config_path: Path) -> BedrockAgentCoreConfigSchema:
|
|
|
58
60
|
# New format
|
|
59
61
|
try:
|
|
60
62
|
return BedrockAgentCoreConfigSchema.model_validate(data)
|
|
63
|
+
except ValidationError as e:
|
|
64
|
+
# Convert Pydantic errors to user-friendly messages
|
|
65
|
+
friendly_errors = []
|
|
66
|
+
for error in e.errors():
|
|
67
|
+
field = ".".join(str(loc) for loc in error["loc"])
|
|
68
|
+
msg = error["msg"]
|
|
69
|
+
# Make common errors more user-friendly
|
|
70
|
+
if "Source path does not exist" in msg:
|
|
71
|
+
friendly_errors.append(f"{field}: {msg} (check if the directory exists)")
|
|
72
|
+
elif "field required" in msg:
|
|
73
|
+
friendly_errors.append(f"{field}: This field is required")
|
|
74
|
+
elif "Input should be" in msg:
|
|
75
|
+
friendly_errors.append(f"{field}: {msg}")
|
|
76
|
+
else:
|
|
77
|
+
friendly_errors.append(f"{field}: {msg}")
|
|
78
|
+
|
|
79
|
+
raise RuntimeToolkitException("Configuration validation failed:\n• " + "\n• ".join(friendly_errors)) from e
|
|
61
80
|
except Exception as e:
|
|
62
|
-
raise
|
|
81
|
+
raise RuntimeToolkitException(f"Invalid configuration format: {e}") from e
|
|
63
82
|
|
|
64
83
|
|
|
65
84
|
def save_config(config: BedrockAgentCoreConfigSchema, config_path: Path):
|
|
@@ -127,3 +146,26 @@ def merge_agent_config(
|
|
|
127
146
|
config.default_agent = agent_name
|
|
128
147
|
|
|
129
148
|
return config
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def get_agentcore_directory(project_root: Path, agent_name: str, source_path: Optional[str] = None) -> Path:
|
|
152
|
+
"""Get the agentcore directory for an agent's build artifacts.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
project_root: Project root directory (typically Path.cwd())
|
|
156
|
+
agent_name: Name of the agent
|
|
157
|
+
source_path: Optional source path configuration
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
Path to agentcore directory:
|
|
161
|
+
- If source_path provided: {project_root}/.bedrock_agentcore/{agent_name}/
|
|
162
|
+
- Otherwise: {project_root}/ (legacy single-agent behavior)
|
|
163
|
+
"""
|
|
164
|
+
if source_path:
|
|
165
|
+
# Multi-agent support: use .bedrock_agentcore/{agent_name}/ for artifact isolation
|
|
166
|
+
agentcore_dir = project_root / ".bedrock_agentcore" / agent_name
|
|
167
|
+
agentcore_dir.mkdir(parents=True, exist_ok=True)
|
|
168
|
+
return agentcore_dir
|
|
169
|
+
else:
|
|
170
|
+
# Legacy single-agent: artifacts at project root
|
|
171
|
+
return project_root
|
|
@@ -8,10 +8,13 @@ from pathlib import Path
|
|
|
8
8
|
from typing import List, Optional, Tuple
|
|
9
9
|
|
|
10
10
|
from jinja2 import Template
|
|
11
|
+
from rich.console import Console
|
|
11
12
|
|
|
12
|
-
from ...cli.common import _handle_warn
|
|
13
|
+
from ...cli.common import _handle_warn, _print_success
|
|
13
14
|
from .entrypoint import detect_dependencies, get_python_version
|
|
14
15
|
|
|
16
|
+
console = Console()
|
|
17
|
+
|
|
15
18
|
log = logging.getLogger(__name__)
|
|
16
19
|
|
|
17
20
|
|
|
@@ -40,11 +43,10 @@ class ContainerRuntime:
|
|
|
40
43
|
break
|
|
41
44
|
else:
|
|
42
45
|
# Informational message - default CodeBuild deployment works fine
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"💡 For local builds, install Docker, Finch, or Podman"
|
|
46
|
+
console.print("\n💡 [cyan]No container engine found (Docker/Finch/Podman not installed)[/cyan]")
|
|
47
|
+
_print_success(
|
|
48
|
+
"Default deployment uses CodeBuild (no container engine needed), "
|
|
49
|
+
"For local builds, install Docker, Finch, or Podman"
|
|
48
50
|
)
|
|
49
51
|
self.runtime = "none"
|
|
50
52
|
self.has_local_runtime = False
|
|
@@ -55,10 +57,9 @@ class ContainerRuntime:
|
|
|
55
57
|
else:
|
|
56
58
|
# Convert hard error to warning - suggest CodeBuild instead
|
|
57
59
|
_handle_warn(
|
|
58
|
-
f"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
f"💡 For local builds, please install {runtime_type.capitalize()}"
|
|
60
|
+
f"{runtime_type.capitalize()} is not installed\n"
|
|
61
|
+
"Recommendation: Use CodeBuild for building containers in the cloud\n"
|
|
62
|
+
f"For local builds, please install {runtime_type.capitalize()}"
|
|
62
63
|
)
|
|
63
64
|
self.runtime = "none"
|
|
64
65
|
self.has_local_runtime = False
|
|
@@ -110,17 +111,32 @@ class ContainerRuntime:
|
|
|
110
111
|
requirements_file: Optional[str] = None,
|
|
111
112
|
memory_id: Optional[str] = None,
|
|
112
113
|
memory_name: Optional[str] = None,
|
|
114
|
+
source_path: Optional[str] = None,
|
|
113
115
|
protocol: Optional[str] = None,
|
|
114
116
|
) -> Path:
|
|
115
|
-
"""Generate Dockerfile from template.
|
|
117
|
+
"""Generate Dockerfile from template.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
agent_path: Path to agent entrypoint file
|
|
121
|
+
output_dir: Output directory for Dockerfile (project root)
|
|
122
|
+
agent_name: Name of the agent
|
|
123
|
+
aws_region: AWS region
|
|
124
|
+
enable_observability: Whether to enable observability
|
|
125
|
+
requirements_file: Optional explicit requirements file path
|
|
126
|
+
memory_id: Optional memory ID
|
|
127
|
+
memory_name: Optional memory name
|
|
128
|
+
source_path: Optional source code directory (for dependency detection)
|
|
129
|
+
protocol: Optional protocol configuration (HTTP or HTTPS)
|
|
130
|
+
"""
|
|
116
131
|
current_platform = self._get_current_platform()
|
|
117
132
|
required_platform = self.DEFAULT_PLATFORM
|
|
118
133
|
|
|
119
134
|
if current_platform != required_platform:
|
|
120
135
|
_handle_warn(
|
|
121
|
-
f"
|
|
122
|
-
f"but Bedrock AgentCore requires '{required_platform}'.\n"
|
|
123
|
-
"
|
|
136
|
+
f"Platform mismatch: Current system is '{current_platform}' "
|
|
137
|
+
f"but Bedrock AgentCore requires '{required_platform}', so local builds won't work.\n"
|
|
138
|
+
"Please use default launch command which will do a remote cross-platform build using code build."
|
|
139
|
+
"For deployment other options and workarounds, see: "
|
|
124
140
|
"https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/getting-started-custom.html\n"
|
|
125
141
|
)
|
|
126
142
|
|
|
@@ -133,23 +149,39 @@ class ContainerRuntime:
|
|
|
133
149
|
with open(template_path) as f:
|
|
134
150
|
template = Template(f.read())
|
|
135
151
|
|
|
152
|
+
# Calculate build context root first (needed for validation)
|
|
153
|
+
# If source_path provided: module path relative to source_path (Docker build context)
|
|
154
|
+
# Otherwise: module path relative to project root
|
|
155
|
+
build_context_root = Path(source_path) if source_path else output_dir
|
|
136
156
|
# Generate .dockerignore if it doesn't exist
|
|
137
|
-
self._ensure_dockerignore(
|
|
157
|
+
self._ensure_dockerignore(build_context_root)
|
|
138
158
|
|
|
139
|
-
# Validate module path
|
|
140
|
-
self._validate_module_path(agent_path,
|
|
159
|
+
# Validate module path against build context root
|
|
160
|
+
self._validate_module_path(agent_path, build_context_root)
|
|
141
161
|
|
|
142
|
-
# Calculate module path relative to
|
|
143
|
-
agent_module_path = self._get_module_path(agent_path,
|
|
162
|
+
# Calculate module path relative to Docker build context
|
|
163
|
+
agent_module_path = self._get_module_path(agent_path, build_context_root)
|
|
144
164
|
|
|
145
165
|
wheelhouse_dir = output_dir / "wheelhouse"
|
|
146
166
|
|
|
147
|
-
# Detect dependencies
|
|
148
|
-
|
|
167
|
+
# Detect dependencies:
|
|
168
|
+
# - If source_path provided: check source_path only
|
|
169
|
+
# - Otherwise: check project root (output_dir)
|
|
170
|
+
# - If explicit requirements_file provided: use that regardless
|
|
171
|
+
if source_path and not requirements_file:
|
|
172
|
+
source_dir = Path(source_path)
|
|
173
|
+
deps = detect_dependencies(source_dir, explicit_file=None)
|
|
174
|
+
if source_path:
|
|
175
|
+
source_dir = Path(source_path)
|
|
176
|
+
deps = detect_dependencies(source_dir, explicit_file=requirements_file)
|
|
177
|
+
else:
|
|
178
|
+
deps = detect_dependencies(output_dir, explicit_file=requirements_file)
|
|
149
179
|
|
|
150
180
|
# Add logic to avoid duplicate installation
|
|
181
|
+
# Check for pyproject.toml in the appropriate directory
|
|
151
182
|
has_current_package = False
|
|
152
|
-
|
|
183
|
+
check_dir = Path(source_path) if source_path else output_dir
|
|
184
|
+
if (check_dir / "pyproject.toml").exists():
|
|
153
185
|
# Only install current package if deps isn't already pointing to it
|
|
154
186
|
if not (deps.found and deps.is_root_package):
|
|
155
187
|
has_current_package = True
|
|
@@ -189,6 +221,7 @@ class ContainerRuntime:
|
|
|
189
221
|
"""Validate that the agent path can be converted to a valid Python module path."""
|
|
190
222
|
try:
|
|
191
223
|
agent_path = agent_path.resolve()
|
|
224
|
+
project_root = project_root.resolve()
|
|
192
225
|
relative_path = agent_path.relative_to(project_root)
|
|
193
226
|
for part in relative_path.parts[:-1]: # Check all directory parts
|
|
194
227
|
if "-" in part:
|
|
@@ -206,6 +239,7 @@ class ContainerRuntime:
|
|
|
206
239
|
"""Get the Python module path for the agent file."""
|
|
207
240
|
try:
|
|
208
241
|
agent_path = agent_path.resolve()
|
|
242
|
+
project_root = project_root.resolve()
|
|
209
243
|
# Get relative path from project root
|
|
210
244
|
relative_path = agent_path.relative_to(project_root)
|
|
211
245
|
# Convert to module path (e.g., src/agents/my_agent.py -> src.agents.my_agent)
|
|
@@ -229,8 +263,21 @@ class ContainerRuntime:
|
|
|
229
263
|
arch = arch_map.get(machine, machine)
|
|
230
264
|
return f"linux/{arch}"
|
|
231
265
|
|
|
232
|
-
def build(
|
|
233
|
-
|
|
266
|
+
def build(
|
|
267
|
+
self,
|
|
268
|
+
build_context: Path,
|
|
269
|
+
tag: str,
|
|
270
|
+
dockerfile_path: Optional[Path] = None,
|
|
271
|
+
platform: Optional[str] = None,
|
|
272
|
+
) -> Tuple[bool, List[str]]:
|
|
273
|
+
"""Build container image.
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
build_context: Directory to use as build context
|
|
277
|
+
tag: Tag for the built image
|
|
278
|
+
dockerfile_path: Optional path to Dockerfile (if not in build_context)
|
|
279
|
+
platform: Optional platform override
|
|
280
|
+
"""
|
|
234
281
|
if not self.has_local_runtime:
|
|
235
282
|
return False, [
|
|
236
283
|
"No container runtime available for local build",
|
|
@@ -239,17 +286,29 @@ class ContainerRuntime:
|
|
|
239
286
|
"💡 For local builds, please install Docker, Finch, or Podman",
|
|
240
287
|
]
|
|
241
288
|
|
|
242
|
-
if not
|
|
243
|
-
return False, [f"
|
|
289
|
+
if not build_context.exists():
|
|
290
|
+
return False, [f"Build context directory not found: {build_context}"]
|
|
244
291
|
|
|
245
|
-
|
|
246
|
-
if
|
|
247
|
-
|
|
292
|
+
# Determine Dockerfile location
|
|
293
|
+
if dockerfile_path:
|
|
294
|
+
# Use provided Dockerfile path
|
|
295
|
+
if not dockerfile_path.exists():
|
|
296
|
+
return False, [f"Dockerfile not found: {dockerfile_path}"]
|
|
297
|
+
else:
|
|
298
|
+
# Look for Dockerfile in build context
|
|
299
|
+
dockerfile_path = build_context / "Dockerfile"
|
|
300
|
+
if not dockerfile_path.exists():
|
|
301
|
+
return False, [f"Dockerfile not found in {build_context}"]
|
|
248
302
|
|
|
249
303
|
cmd = [self.runtime, "build", "-t", tag]
|
|
304
|
+
|
|
305
|
+
# Use -f flag if Dockerfile is not in the build context
|
|
306
|
+
if dockerfile_path.parent != build_context:
|
|
307
|
+
cmd.extend(["-f", str(dockerfile_path)])
|
|
308
|
+
|
|
250
309
|
build_platform = platform or self.DEFAULT_PLATFORM
|
|
251
310
|
cmd.extend(["--platform", build_platform])
|
|
252
|
-
cmd.append(str(
|
|
311
|
+
cmd.append(str(build_context))
|
|
253
312
|
|
|
254
313
|
return self._execute_command(cmd)
|
|
255
314
|
|
|
@@ -5,10 +5,17 @@ from typing import Dict, List, Literal, Optional
|
|
|
5
5
|
from pydantic import BaseModel, Field, field_validator
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
class NetworkModeConfig(BaseModel):
|
|
9
|
+
"""Network mode configuration for VPC deployments."""
|
|
10
|
+
|
|
11
|
+
security_groups: List[str] = Field(default_factory=list, description="List of security group IDs")
|
|
12
|
+
subnets: List[str] = Field(default_factory=list, description="List of subnet IDs")
|
|
13
|
+
|
|
14
|
+
|
|
8
15
|
class MemoryConfig(BaseModel):
|
|
9
16
|
"""Memory configuration for BedrockAgentCore."""
|
|
10
17
|
|
|
11
|
-
mode: Literal["STM_ONLY", "STM_AND_LTM"] = Field(
|
|
18
|
+
mode: Literal["STM_ONLY", "STM_AND_LTM", "NO_MEMORY"] = Field(
|
|
12
19
|
default="STM_ONLY", description="Memory mode - always has STM, optionally adds LTM"
|
|
13
20
|
)
|
|
14
21
|
memory_id: Optional[str] = Field(default=None, description="Memory resource ID")
|
|
@@ -18,11 +25,14 @@ class MemoryConfig(BaseModel):
|
|
|
18
25
|
first_invoke_memory_check_done: bool = Field(
|
|
19
26
|
default=False, description="Whether first invoke memory check has been performed"
|
|
20
27
|
)
|
|
28
|
+
was_created_by_toolkit: bool = Field(
|
|
29
|
+
default=False, description="Whether memory was created by toolkit (vs reused existing)"
|
|
30
|
+
)
|
|
21
31
|
|
|
22
32
|
@property
|
|
23
33
|
def is_enabled(self) -> bool:
|
|
24
|
-
"""Check if memory is enabled
|
|
25
|
-
return
|
|
34
|
+
"""Check if memory is enabled."""
|
|
35
|
+
return self.mode != "NO_MEMORY"
|
|
26
36
|
|
|
27
37
|
@property
|
|
28
38
|
def has_ltm(self) -> bool:
|
|
@@ -34,10 +44,38 @@ class NetworkConfiguration(BaseModel):
|
|
|
34
44
|
"""Network configuration for BedrockAgentCore deployment."""
|
|
35
45
|
|
|
36
46
|
network_mode: str = Field(default="PUBLIC", description="Network mode for deployment")
|
|
47
|
+
network_mode_config: Optional[NetworkModeConfig] = Field(
|
|
48
|
+
default=None, description="Network mode configuration (required for VPC mode)"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
@field_validator("network_mode")
|
|
52
|
+
@classmethod
|
|
53
|
+
def validate_network_mode(cls, v: str) -> str:
|
|
54
|
+
"""Validate network mode and ensure VPC config is provided when needed."""
|
|
55
|
+
valid_modes = ["PUBLIC", "VPC"]
|
|
56
|
+
if v not in valid_modes:
|
|
57
|
+
raise ValueError(f"Invalid network_mode: {v}. Must be one of {valid_modes}")
|
|
58
|
+
return v
|
|
59
|
+
|
|
60
|
+
@field_validator("network_mode_config")
|
|
61
|
+
@classmethod
|
|
62
|
+
def validate_network_mode_config(cls, v: Optional[NetworkModeConfig], info) -> Optional[NetworkModeConfig]:
|
|
63
|
+
"""Validate that network_mode_config is provided when network_mode is VPC."""
|
|
64
|
+
if info.data.get("network_mode") == "VPC" and v is None:
|
|
65
|
+
raise ValueError("network_mode_config is required when network_mode is VPC")
|
|
66
|
+
return v
|
|
37
67
|
|
|
38
68
|
def to_aws_dict(self) -> dict:
|
|
39
69
|
"""Convert to AWS API format with camelCase keys."""
|
|
40
|
-
|
|
70
|
+
result = {"networkMode": self.network_mode}
|
|
71
|
+
|
|
72
|
+
if self.network_mode_config:
|
|
73
|
+
result["networkModeConfig"] = {
|
|
74
|
+
"securityGroups": self.network_mode_config.security_groups,
|
|
75
|
+
"subnets": self.network_mode_config.subnets,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return result
|
|
41
79
|
|
|
42
80
|
|
|
43
81
|
class ProtocolConfiguration(BaseModel):
|
|
@@ -113,6 +151,7 @@ class BedrockAgentCoreAgentSchema(BaseModel):
|
|
|
113
151
|
entrypoint: str = Field(..., description="Entrypoint file path")
|
|
114
152
|
platform: str = Field(default="linux/amd64", description="Target platform")
|
|
115
153
|
container_runtime: str = Field(default="docker", description="Container runtime to use")
|
|
154
|
+
source_path: Optional[str] = Field(default=None, description="Directory containing agent source code")
|
|
116
155
|
aws: AWSConfig = Field(default_factory=AWSConfig)
|
|
117
156
|
bedrock_agentcore: BedrockAgentCoreDeploymentInfo = Field(default_factory=BedrockAgentCoreDeploymentInfo)
|
|
118
157
|
codebuild: CodeBuildConfig = Field(default_factory=CodeBuildConfig)
|
|
@@ -4,6 +4,7 @@ WORKDIR /app
|
|
|
4
4
|
# All environment variables in one layer
|
|
5
5
|
ENV UV_SYSTEM_PYTHON=1 \
|
|
6
6
|
UV_COMPILE_BYTECODE=1 \
|
|
7
|
+
UV_NO_PROGRESS=1 \
|
|
7
8
|
PYTHONUNBUFFERED=1 \
|
|
8
9
|
DOCKER_CONTAINER=1{% if aws_region %} \
|
|
9
10
|
AWS_REGION={{ aws_region }} \
|
|
@@ -27,12 +28,6 @@ RUN uv pip install -r {{ dependencies_file }}
|
|
|
27
28
|
RUN uv pip install aws-opentelemetry-distro>=0.10.1
|
|
28
29
|
{% endif %}
|
|
29
30
|
|
|
30
|
-
# Set AWS region environment variable
|
|
31
|
-
{% if aws_region %}
|
|
32
|
-
ENV AWS_REGION={{ aws_region }}
|
|
33
|
-
ENV AWS_DEFAULT_REGION={{ aws_region }}
|
|
34
|
-
{% endif %}
|
|
35
|
-
|
|
36
31
|
# Signal that this is running in Docker for host binding logic
|
|
37
32
|
ENV DOCKER_CONTAINER=1
|
|
38
33
|
|
|
@@ -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.23
|
|
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
|
|
@@ -22,7 +22,7 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
|
22
22
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
23
|
Requires-Python: >=3.10
|
|
24
24
|
Requires-Dist: autopep8>=2.3.2
|
|
25
|
-
Requires-Dist: bedrock-agentcore>=0.1.
|
|
25
|
+
Requires-Dist: bedrock-agentcore>=0.1.7
|
|
26
26
|
Requires-Dist: boto3>=1.40.35
|
|
27
27
|
Requires-Dist: botocore>=1.40.35
|
|
28
28
|
Requires-Dist: docstring-parser<1.0,>=0.15
|