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.

Files changed (25) hide show
  1. bedrock_agentcore_starter_toolkit/cli/common.py +1 -1
  2. bedrock_agentcore_starter_toolkit/cli/runtime/commands.py +167 -51
  3. bedrock_agentcore_starter_toolkit/cli/runtime/configuration_manager.py +45 -17
  4. bedrock_agentcore_starter_toolkit/notebook/runtime/bedrock_agentcore.py +30 -1
  5. bedrock_agentcore_starter_toolkit/operations/memory/manager.py +1 -1
  6. bedrock_agentcore_starter_toolkit/operations/runtime/__init__.py +12 -1
  7. bedrock_agentcore_starter_toolkit/operations/runtime/configure.py +182 -29
  8. bedrock_agentcore_starter_toolkit/operations/runtime/destroy.py +24 -7
  9. bedrock_agentcore_starter_toolkit/operations/runtime/exceptions.py +27 -0
  10. bedrock_agentcore_starter_toolkit/operations/runtime/invoke.py +12 -3
  11. bedrock_agentcore_starter_toolkit/operations/runtime/launch.py +99 -44
  12. bedrock_agentcore_starter_toolkit/operations/runtime/status.py +5 -4
  13. bedrock_agentcore_starter_toolkit/services/codebuild.py +53 -26
  14. bedrock_agentcore_starter_toolkit/services/ecr.py +44 -2
  15. bedrock_agentcore_starter_toolkit/utils/runtime/config.py +43 -1
  16. bedrock_agentcore_starter_toolkit/utils/runtime/container.py +89 -30
  17. bedrock_agentcore_starter_toolkit/utils/runtime/schema.py +43 -4
  18. bedrock_agentcore_starter_toolkit/utils/runtime/templates/Dockerfile.j2 +1 -6
  19. bedrock_agentcore_starter_toolkit/utils/runtime/templates/dockerignore.template +1 -0
  20. {bedrock_agentcore_starter_toolkit-0.1.21.dist-info → bedrock_agentcore_starter_toolkit-0.1.23.dist-info}/METADATA +2 -2
  21. {bedrock_agentcore_starter_toolkit-0.1.21.dist-info → bedrock_agentcore_starter_toolkit-0.1.23.dist-info}/RECORD +25 -24
  22. {bedrock_agentcore_starter_toolkit-0.1.21.dist-info → bedrock_agentcore_starter_toolkit-0.1.23.dist-info}/WHEEL +0 -0
  23. {bedrock_agentcore_starter_toolkit-0.1.21.dist-info → bedrock_agentcore_starter_toolkit-0.1.23.dist-info}/entry_points.txt +0 -0
  24. {bedrock_agentcore_starter_toolkit-0.1.21.dist-info → bedrock_agentcore_starter_toolkit-0.1.23.dist-info}/licenses/LICENSE.txt +0 -0
  25. {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 agent_config.memory and agent_config.memory.memory_id:
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 current directory to S3, respecting .dockerignore patterns."""
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
- for root, dirs, files in os.walk("."):
86
- # Convert to relative path
87
- rel_root = os.path.relpath(root, ".")
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 file and return list of patterns."""
284
- dockerignore_path = Path(".dockerignore")
285
- patterns = []
286
-
287
- if dockerignore_path.exists():
288
- with open(dockerignore_path, "r") as f:
289
- for line in f:
290
- line = line.strip()
291
- if line and not line.startswith("#"):
292
- patterns.append(line)
293
-
294
- self.logger.info("Using .dockerignore with %d patterns", len(patterns))
295
- else:
296
- # Default patterns if no .dockerignore
297
- patterns = [
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 ValueError(f"Invalid configuration format: {e}") from e
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
- _handle_warn(
44
- "ℹ️ No container engine found (Docker/Finch/Podman not installed)\n"
45
- "Default deployment uses CodeBuild (no container engine needed)\n"
46
- "💡 Run 'agentcore launch' for cloud-based building and deployment\n"
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"⚠️ {runtime_type.capitalize()} is not installed\n"
59
- "💡 Recommendation: Use CodeBuild for building containers in the cloud\n"
60
- "💡 Run 'agentcore launch' (default) for CodeBuild deployment\n"
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"[WARNING] Platform mismatch: Current system is '{current_platform}' "
122
- f"but Bedrock AgentCore requires '{required_platform}'.\n"
123
- "For deployment options and workarounds, see: "
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(output_dir)
157
+ self._ensure_dockerignore(build_context_root)
138
158
 
139
- # Validate module path before generating Dockerfile
140
- self._validate_module_path(agent_path, output_dir)
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 project root
143
- agent_module_path = self._get_module_path(agent_path, output_dir)
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 using the new DependencyInfo class
148
- deps = detect_dependencies(output_dir, explicit_file=requirements_file)
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
- if (output_dir / "pyproject.toml").exists():
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(self, dockerfile_dir: Path, tag: str, platform: Optional[str] = None) -> Tuple[bool, List[str]]:
233
- """Build container image."""
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 dockerfile_dir.exists():
243
- return False, [f"Directory not found: {dockerfile_dir}"]
289
+ if not build_context.exists():
290
+ return False, [f"Build context directory not found: {build_context}"]
244
291
 
245
- dockerfile_path = dockerfile_dir / "Dockerfile"
246
- if not dockerfile_path.exists():
247
- return False, [f"Dockerfile not found in {dockerfile_dir}"]
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(dockerfile_dir))
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 (always true now)."""
25
- return True
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
- return {"networkMode": self.network_mode}
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
 
@@ -63,6 +63,7 @@ tests/
63
63
  # Bedrock AgentCore specific - keep config but exclude runtime files
64
64
  .bedrock_agentcore.yaml
65
65
  .dockerignore
66
+ .bedrock_agentcore/
66
67
 
67
68
  # Keep wheelhouse for offline installations
68
69
  # wheelhouse/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bedrock-agentcore-starter-toolkit
3
- Version: 0.1.21
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.4
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