bedrock-agentcore-starter-toolkit 0.1.22__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 +157 -58
- bedrock_agentcore_starter_toolkit/cli/runtime/configuration_manager.py +45 -16
- bedrock_agentcore_starter_toolkit/notebook/runtime/bedrock_agentcore.py +27 -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 +145 -22
- bedrock_agentcore_starter_toolkit/operations/runtime/destroy.py +22 -6
- bedrock_agentcore_starter_toolkit/operations/runtime/invoke.py +12 -3
- bedrock_agentcore_starter_toolkit/operations/runtime/launch.py +18 -3
- bedrock_agentcore_starter_toolkit/operations/runtime/status.py +5 -4
- bedrock_agentcore_starter_toolkit/services/codebuild.py +2 -1
- bedrock_agentcore_starter_toolkit/services/ecr.py +44 -2
- bedrock_agentcore_starter_toolkit/utils/runtime/container.py +12 -9
- bedrock_agentcore_starter_toolkit/utils/runtime/schema.py +6 -35
- bedrock_agentcore_starter_toolkit/utils/runtime/templates/Dockerfile.j2 +0 -6
- {bedrock_agentcore_starter_toolkit-0.1.22.dist-info → bedrock_agentcore_starter_toolkit-0.1.23.dist-info}/METADATA +1 -1
- {bedrock_agentcore_starter_toolkit-0.1.22.dist-info → bedrock_agentcore_starter_toolkit-0.1.23.dist-info}/RECORD +22 -22
- {bedrock_agentcore_starter_toolkit-0.1.22.dist-info → bedrock_agentcore_starter_toolkit-0.1.23.dist-info}/WHEEL +0 -0
- {bedrock_agentcore_starter_toolkit-0.1.22.dist-info → bedrock_agentcore_starter_toolkit-0.1.23.dist-info}/entry_points.txt +0 -0
- {bedrock_agentcore_starter_toolkit-0.1.22.dist-info → bedrock_agentcore_starter_toolkit-0.1.23.dist-info}/licenses/LICENSE.txt +0 -0
- {bedrock_agentcore_starter_toolkit-0.1.22.dist-info → bedrock_agentcore_starter_toolkit-0.1.23.dist-info}/licenses/NOTICE.txt +0 -0
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
"""Configure operation - creates BedrockAgentCore configuration and Dockerfile."""
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
+
import os
|
|
4
5
|
import re
|
|
5
6
|
from pathlib import Path
|
|
6
|
-
from typing import Any, Dict, Optional, Tuple
|
|
7
|
+
from typing import Any, Dict, Literal, Optional, Tuple
|
|
7
8
|
|
|
8
9
|
from ...cli.runtime.configuration_manager import ConfigurationManager
|
|
9
10
|
from ...services.ecr import get_account_id, get_region
|
|
10
11
|
from ...utils.runtime.config import merge_agent_config, save_config
|
|
11
12
|
from ...utils.runtime.container import ContainerRuntime
|
|
13
|
+
from ...utils.runtime.entrypoint import detect_dependencies
|
|
12
14
|
from ...utils.runtime.schema import (
|
|
13
15
|
AWSConfig,
|
|
14
16
|
BedrockAgentCoreAgentSchema,
|
|
@@ -24,6 +26,103 @@ from .models import ConfigureResult
|
|
|
24
26
|
log = logging.getLogger(__name__)
|
|
25
27
|
|
|
26
28
|
|
|
29
|
+
def get_relative_path(path: Path, base: Optional[Path] = None) -> str:
|
|
30
|
+
"""Convert path to relative format with OS-native separators.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
path: Absolute or relative path
|
|
34
|
+
base: Base directory (defaults to current working directory)
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Path relative to base with OS-native separators
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
ValueError: If path is empty or invalid
|
|
41
|
+
"""
|
|
42
|
+
# Validate input
|
|
43
|
+
if not path or str(path).strip() == "":
|
|
44
|
+
raise ValueError("Path cannot be empty")
|
|
45
|
+
|
|
46
|
+
# Ensure path is a Path object
|
|
47
|
+
path_obj = Path(path) if not isinstance(path, Path) else path
|
|
48
|
+
base = base or Path.cwd()
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
rel_path = path_obj.relative_to(base)
|
|
52
|
+
return str(rel_path)
|
|
53
|
+
except ValueError:
|
|
54
|
+
# Path is outside base - keep full path for clarity
|
|
55
|
+
# Don't lose directory structure by showing just the filename
|
|
56
|
+
return str(path_obj)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def detect_entrypoint(source_path: Path) -> Optional[Path]:
|
|
60
|
+
"""Detect entrypoint file in source directory.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
source_path: Directory to search for entrypoint
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Path to detected entrypoint file, or None if not found
|
|
67
|
+
"""
|
|
68
|
+
ENTRYPOINT_CANDIDATES = ["agent.py", "app.py", "main.py", "__main__.py"]
|
|
69
|
+
|
|
70
|
+
source_dir = Path(source_path)
|
|
71
|
+
for candidate in ENTRYPOINT_CANDIDATES:
|
|
72
|
+
candidate_path = source_dir / candidate
|
|
73
|
+
if candidate_path.exists():
|
|
74
|
+
log.debug("Detected entrypoint: %s", candidate_path)
|
|
75
|
+
return candidate_path
|
|
76
|
+
|
|
77
|
+
log.debug("No entrypoint found in %s", source_path)
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def detect_requirements(source_path: Path):
|
|
82
|
+
"""Detect requirements file in the source directory.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
source_path: Source directory (where entrypoint is located)
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
DependencyInfo object with detection results
|
|
89
|
+
"""
|
|
90
|
+
# Resolve to absolute path for consistent behavior
|
|
91
|
+
source_path_resolved = Path(source_path).resolve()
|
|
92
|
+
log.debug("Checking for requirements in source directory: %s", source_path_resolved)
|
|
93
|
+
|
|
94
|
+
deps = detect_dependencies(source_path_resolved)
|
|
95
|
+
if deps.found:
|
|
96
|
+
log.debug("Found requirements in source directory: %s", deps.resolved_path)
|
|
97
|
+
else:
|
|
98
|
+
log.debug("No requirements file found in source directory: %s", source_path_resolved)
|
|
99
|
+
|
|
100
|
+
return deps
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def infer_agent_name(entrypoint_path: Path, base: Optional[Path] = None) -> str:
|
|
104
|
+
"""Infer agent name from entrypoint path.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
entrypoint_path: Path to agent entrypoint file
|
|
108
|
+
base: Base directory for relative path (defaults to cwd)
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Suggested agent name (e.g., 'agents_writer_main' from 'agents/writer/main.py')
|
|
112
|
+
"""
|
|
113
|
+
rel_entrypoint = get_relative_path(entrypoint_path, base)
|
|
114
|
+
|
|
115
|
+
# Remove .py extension if present (only at the end)
|
|
116
|
+
if rel_entrypoint.endswith(".py"):
|
|
117
|
+
rel_entrypoint = rel_entrypoint[:-3]
|
|
118
|
+
|
|
119
|
+
# Replace spaces and OS path separators with underscores
|
|
120
|
+
suggested_name = rel_entrypoint.replace(" ", "_").replace(os.sep, "_")
|
|
121
|
+
|
|
122
|
+
log.debug("Inferred agent name: %s from %s", suggested_name, get_relative_path(entrypoint_path, base))
|
|
123
|
+
return suggested_name
|
|
124
|
+
|
|
125
|
+
|
|
27
126
|
def configure_bedrock_agentcore(
|
|
28
127
|
agent_name: str,
|
|
29
128
|
entrypoint_path: Path,
|
|
@@ -34,6 +133,7 @@ def configure_bedrock_agentcore(
|
|
|
34
133
|
auto_create_ecr: bool = True,
|
|
35
134
|
auto_create_execution_role: bool = True,
|
|
36
135
|
enable_observability: bool = True,
|
|
136
|
+
memory_mode: Literal["NO_MEMORY", "STM_ONLY", "STM_AND_LTM"] = "STM_ONLY",
|
|
37
137
|
requirements_file: Optional[str] = None,
|
|
38
138
|
authorizer_configuration: Optional[Dict[str, Any]] = None,
|
|
39
139
|
request_header_configuration: Optional[Dict[str, Any]] = None,
|
|
@@ -55,6 +155,7 @@ def configure_bedrock_agentcore(
|
|
|
55
155
|
auto_create_ecr: Whether to auto-create ECR repository
|
|
56
156
|
auto_create_execution_role: Whether to auto-create execution role if not provided
|
|
57
157
|
enable_observability: Whether to enable observability
|
|
158
|
+
memory_mode: Memory configuration mode - "NO_MEMORY", "STM_ONLY" (default), or "STM_AND_LTM"
|
|
58
159
|
requirements_file: Path to requirements file
|
|
59
160
|
authorizer_configuration: JWT authorizer configuration dictionary
|
|
60
161
|
request_header_configuration: Request header configuration dictionary
|
|
@@ -121,32 +222,52 @@ def configure_bedrock_agentcore(
|
|
|
121
222
|
else:
|
|
122
223
|
log.debug("No execution role provided and auto-create disabled")
|
|
123
224
|
|
|
124
|
-
|
|
125
|
-
log.debug("Prompting for memory configuration")
|
|
126
|
-
|
|
225
|
+
# Pass region to ConfigurationManager so it can check for existing memories
|
|
127
226
|
config_manager = ConfigurationManager(build_dir / ".bedrock_agentcore.yaml", non_interactive)
|
|
128
227
|
|
|
129
|
-
#
|
|
130
|
-
action, value = config_manager.prompt_memory_selection()
|
|
131
|
-
|
|
228
|
+
# Handle memory configuration
|
|
132
229
|
memory_config = MemoryConfig()
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
memory_config.mode = "
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
memory_config.mode = value # This is the mode (STM_ONLY, STM_AND_LTM, NO_MEMORY)
|
|
230
|
+
|
|
231
|
+
# Check if memory is explicitly disabled FIRST (works in both interactive and non-interactive modes)
|
|
232
|
+
if memory_mode == "NO_MEMORY":
|
|
233
|
+
memory_config.mode = "NO_MEMORY"
|
|
234
|
+
log.info("Memory disabled")
|
|
235
|
+
elif non_interactive:
|
|
236
|
+
# Non-interactive mode: use explicit memory_mode parameter
|
|
237
|
+
memory_config.mode = memory_mode
|
|
142
238
|
memory_config.event_expiry_days = 30
|
|
143
239
|
memory_config.memory_name = f"{agent_name}_memory"
|
|
144
|
-
log.info("Will create new memory with mode: %s",
|
|
240
|
+
log.info("Will create new memory with mode: %s", memory_mode)
|
|
145
241
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
242
|
+
if memory_mode == "STM_AND_LTM":
|
|
243
|
+
log.info("Memory configuration: Short-term + Long-term memory enabled")
|
|
244
|
+
else: # STM_ONLY
|
|
245
|
+
log.info("Memory configuration: Short-term memory only")
|
|
246
|
+
else:
|
|
247
|
+
# Interactive mode: prompt user (only if memory not explicitly disabled)
|
|
248
|
+
action, value = config_manager.prompt_memory_selection()
|
|
249
|
+
|
|
250
|
+
if action == "USE_EXISTING":
|
|
251
|
+
# Using existing memory - just store the ID
|
|
252
|
+
memory_config.memory_id = value
|
|
253
|
+
memory_config.mode = "STM_AND_LTM" # Assume existing has strategies
|
|
254
|
+
memory_config.memory_name = f"{agent_name}_memory"
|
|
255
|
+
log.info("Using existing memory resource: %s", value)
|
|
256
|
+
elif action == "CREATE_NEW":
|
|
257
|
+
# Create new with specified mode
|
|
258
|
+
memory_config.mode = value
|
|
259
|
+
memory_config.event_expiry_days = 30
|
|
260
|
+
memory_config.memory_name = f"{agent_name}_memory"
|
|
261
|
+
log.info("Will create new memory with mode: %s", value)
|
|
262
|
+
|
|
263
|
+
if value == "STM_AND_LTM":
|
|
264
|
+
log.info("Memory configuration: Short-term + Long-term memory enabled")
|
|
265
|
+
else: # STM_ONLY
|
|
266
|
+
log.info("Memory configuration: Short-term memory only")
|
|
267
|
+
elif action == "SKIP":
|
|
268
|
+
# User chose to skip memory setup
|
|
269
|
+
memory_config.mode = "NO_MEMORY"
|
|
270
|
+
log.info("Memory disabled by user choice")
|
|
150
271
|
|
|
151
272
|
# Check for existing memory configuration from previous launch
|
|
152
273
|
config_path = build_dir / ".bedrock_agentcore.yaml"
|
|
@@ -224,7 +345,9 @@ def configure_bedrock_agentcore(
|
|
|
224
345
|
source_path,
|
|
225
346
|
protocol,
|
|
226
347
|
)
|
|
227
|
-
|
|
348
|
+
# Log with relative path for better readability
|
|
349
|
+
rel_dockerfile_path = get_relative_path(Path(dockerfile_path))
|
|
350
|
+
log.info("Generated Dockerfile: %s", rel_dockerfile_path)
|
|
228
351
|
|
|
229
352
|
# Ensure .dockerignore exists at Docker build context location
|
|
230
353
|
if source_path:
|
|
@@ -80,7 +80,16 @@ def destroy_bedrock_agentcore(
|
|
|
80
80
|
_destroy_codebuild_project(session, agent_config, result, dry_run)
|
|
81
81
|
|
|
82
82
|
# 5. Remove memory resource
|
|
83
|
-
|
|
83
|
+
if agent_config.memory and agent_config.memory.memory_id and agent_config.memory.mode != "NO_MEMORY":
|
|
84
|
+
if agent_config.memory.was_created_by_toolkit:
|
|
85
|
+
# Memory was created by toolkit during configure/launch - delete it
|
|
86
|
+
_destroy_memory(session, agent_config, result, dry_run)
|
|
87
|
+
if not dry_run:
|
|
88
|
+
log.info("Deleted memory (was created by toolkit): %s", agent_config.memory.memory_id)
|
|
89
|
+
else:
|
|
90
|
+
# Memory was pre-existing - preserve it
|
|
91
|
+
result.warnings.append(f"Memory {agent_config.memory.memory_id} preserved (was pre-existing)")
|
|
92
|
+
log.info("Preserving pre-existing memory: %s", agent_config.memory.memory_id)
|
|
84
93
|
|
|
85
94
|
# 6. Remove CodeBuild IAM Role
|
|
86
95
|
_destroy_codebuild_iam_role(session, agent_config, result, dry_run)
|
|
@@ -130,10 +139,9 @@ def _destroy_agentcore_endpoint(
|
|
|
130
139
|
endpoint_name = endpoint_response.get("name", "DEFAULT")
|
|
131
140
|
endpoint_arn = endpoint_response.get("agentRuntimeEndpointArn")
|
|
132
141
|
|
|
133
|
-
#
|
|
142
|
+
# DEFAULT endpoint is automatically deleted when agent is deleted
|
|
134
143
|
if endpoint_name == "DEFAULT":
|
|
135
|
-
|
|
136
|
-
log.info("Skipping deletion of DEFAULT endpoint")
|
|
144
|
+
log.info("DEFAULT endpoint will be automatically deleted with agent")
|
|
137
145
|
return
|
|
138
146
|
|
|
139
147
|
if dry_run:
|
|
@@ -147,7 +155,13 @@ def _destroy_agentcore_endpoint(
|
|
|
147
155
|
result.resources_removed.append(f"AgentCore endpoint: {endpoint_arn}")
|
|
148
156
|
log.info("Deleted AgentCore endpoint: %s", endpoint_arn)
|
|
149
157
|
except ClientError as delete_error:
|
|
150
|
-
|
|
158
|
+
error_code = delete_error.response["Error"]["Code"]
|
|
159
|
+
|
|
160
|
+
# Handle ConflictException for DEFAULT endpoint gracefully
|
|
161
|
+
if error_code == "ConflictException":
|
|
162
|
+
log.info("DEFAULT endpoint will be automatically deleted with agent")
|
|
163
|
+
return
|
|
164
|
+
elif error_code not in ["ResourceNotFoundException", "NotFound"]:
|
|
151
165
|
result.errors.append(f"Failed to delete endpoint {endpoint_arn}: {delete_error}")
|
|
152
166
|
log.error("Failed to delete endpoint: %s", delete_error)
|
|
153
167
|
else:
|
|
@@ -369,7 +383,9 @@ def _destroy_codebuild_project(
|
|
|
369
383
|
"""Remove CodeBuild project for this agent."""
|
|
370
384
|
try:
|
|
371
385
|
codebuild_client = session.client("codebuild", region_name=agent_config.aws.region)
|
|
372
|
-
|
|
386
|
+
from ...services.ecr import sanitize_ecr_repo_name
|
|
387
|
+
|
|
388
|
+
project_name = f"bedrock-agentcore-{sanitize_ecr_repo_name(agent_config.name)}-builder"
|
|
373
389
|
|
|
374
390
|
if dry_run:
|
|
375
391
|
result.resources_removed.append(f"CodeBuild project: {project_name} (DRY RUN)")
|
|
@@ -30,10 +30,10 @@ def invoke_bedrock_agentcore(
|
|
|
30
30
|
project_config = load_config(config_path)
|
|
31
31
|
agent_config = project_config.get_agent_config(agent_name)
|
|
32
32
|
|
|
33
|
-
# Check memory status on first invoke if
|
|
33
|
+
# Check memory status on first invoke if memory is enabled (STM or LTM)
|
|
34
34
|
if (
|
|
35
35
|
agent_config.memory
|
|
36
|
-
and agent_config.memory.
|
|
36
|
+
and agent_config.memory.mode != "NO_MEMORY"
|
|
37
37
|
and agent_config.memory.memory_id
|
|
38
38
|
and not agent_config.memory.first_invoke_memory_check_done
|
|
39
39
|
):
|
|
@@ -45,10 +45,19 @@ def invoke_bedrock_agentcore(
|
|
|
45
45
|
memory_status = memory_manager.get_memory_status(agent_config.memory.memory_id)
|
|
46
46
|
|
|
47
47
|
if memory_status != MemoryStatus.ACTIVE.value:
|
|
48
|
+
# Determine memory type for better messaging
|
|
49
|
+
memory_type = "Memory"
|
|
50
|
+
if agent_config.memory.has_ltm:
|
|
51
|
+
memory_type = "Long-term memory"
|
|
52
|
+
time_estimate = "60-180 seconds"
|
|
53
|
+
else:
|
|
54
|
+
memory_type = "Short-term memory"
|
|
55
|
+
time_estimate = "30-90 seconds"
|
|
56
|
+
|
|
48
57
|
# Provide graceful error message
|
|
49
58
|
error_message = (
|
|
50
59
|
f"Memory is still provisioning (current status: {memory_status}). "
|
|
51
|
-
f"
|
|
60
|
+
f"{memory_type} takes {time_estimate} to activate.\n\n"
|
|
52
61
|
f"Please wait and check status with:\n"
|
|
53
62
|
f" agentcore status{f' --agent {agent_name}' if agent_name else ''}"
|
|
54
63
|
)
|
|
@@ -140,7 +140,13 @@ def _ensure_memory_for_agent(
|
|
|
140
140
|
"""Ensure memory resource exists for agent. Returns memory_id or None.
|
|
141
141
|
|
|
142
142
|
This function is idempotent - it creates memory if needed or reuses existing.
|
|
143
|
+
CRITICAL: Never overwrites was_created_by_toolkit flag - that's set by configure.
|
|
143
144
|
"""
|
|
145
|
+
# Check if memory is disabled
|
|
146
|
+
if agent_config.memory and agent_config.memory.mode == "NO_MEMORY":
|
|
147
|
+
log.info("Memory disabled - skipping memory creation")
|
|
148
|
+
return None
|
|
149
|
+
|
|
144
150
|
# If memory already exists, return it
|
|
145
151
|
if agent_config.memory and agent_config.memory.memory_id:
|
|
146
152
|
log.info("Using existing memory: %s", agent_config.memory.memory_id)
|
|
@@ -158,14 +164,16 @@ def _ensure_memory_for_agent(
|
|
|
158
164
|
memory_manager = MemoryManager(region_name=agent_config.aws.region)
|
|
159
165
|
memory_name = f"{agent_name}_mem" # Short name under 48 char limit
|
|
160
166
|
|
|
161
|
-
# Check if memory already exists
|
|
167
|
+
# Check if memory already exists in cloud
|
|
162
168
|
existing_memory = None
|
|
163
169
|
try:
|
|
164
170
|
memories = memory_manager.list_memories()
|
|
165
171
|
for m in memories:
|
|
166
172
|
if m.id.startswith(memory_name):
|
|
167
173
|
existing_memory = memory_manager.get_memory(m.id)
|
|
168
|
-
log.info("Found existing memory: %s", m.id)
|
|
174
|
+
log.info("Found existing memory in cloud: %s", m.id)
|
|
175
|
+
# DO NOT OVERWRITE was_created_by_toolkit flag
|
|
176
|
+
# The flag from configure tells us the user's intent
|
|
169
177
|
break
|
|
170
178
|
except Exception as e:
|
|
171
179
|
log.debug("Error checking for existing memory: %s", e)
|
|
@@ -251,9 +259,16 @@ def _ensure_memory_for_agent(
|
|
|
251
259
|
event_expiry_days=agent_config.memory.event_expiry_days or 30,
|
|
252
260
|
memory_execution_role_arn=None,
|
|
253
261
|
)
|
|
262
|
+
|
|
263
|
+
# Ensure was_created_by_toolkit is True since we just created it
|
|
264
|
+
# (Should already be True from configure if user chose CREATE_NEW)
|
|
265
|
+
if not agent_config.memory.was_created_by_toolkit:
|
|
266
|
+
log.warning("Memory created but flag was False - correcting to True")
|
|
267
|
+
agent_config.memory.was_created_by_toolkit = True
|
|
268
|
+
|
|
254
269
|
log.info("✅ New memory created: %s (provisioning in background)", memory.id)
|
|
255
270
|
|
|
256
|
-
# Save memory configuration
|
|
271
|
+
# Save memory configuration (preserving was_created_by_toolkit flag)
|
|
257
272
|
agent_config.memory.memory_id = memory.id
|
|
258
273
|
agent_config.memory.memory_arn = memory.arn
|
|
259
274
|
agent_config.memory.memory_name = memory_name
|
|
@@ -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:
|
|
@@ -14,6 +14,7 @@ import boto3
|
|
|
14
14
|
from botocore.exceptions import ClientError
|
|
15
15
|
|
|
16
16
|
from ..operations.runtime.create_role import get_or_create_codebuild_execution_role
|
|
17
|
+
from .ecr import sanitize_ecr_repo_name
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
class CodeBuildService:
|
|
@@ -159,7 +160,7 @@ class CodeBuildService:
|
|
|
159
160
|
self, agent_name: str, ecr_repository_uri: str, execution_role: str, source_location: str
|
|
160
161
|
) -> str:
|
|
161
162
|
"""Create or update CodeBuild project for ARM64 builds."""
|
|
162
|
-
project_name = f"bedrock-agentcore-{agent_name}-builder"
|
|
163
|
+
project_name = f"bedrock-agentcore-{sanitize_ecr_repo_name(agent_name)}-builder"
|
|
163
164
|
|
|
164
165
|
buildspec = self._get_arm64_buildspec(ecr_repository_uri)
|
|
165
166
|
|
|
@@ -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
|
|
|
@@ -44,8 +44,10 @@ class ContainerRuntime:
|
|
|
44
44
|
else:
|
|
45
45
|
# Informational message - default CodeBuild deployment works fine
|
|
46
46
|
console.print("\n💡 [cyan]No container engine found (Docker/Finch/Podman not installed)[/cyan]")
|
|
47
|
-
_print_success(
|
|
48
|
-
|
|
47
|
+
_print_success(
|
|
48
|
+
"Default deployment uses CodeBuild (no container engine needed), "
|
|
49
|
+
"For local builds, install Docker, Finch, or Podman"
|
|
50
|
+
)
|
|
49
51
|
self.runtime = "none"
|
|
50
52
|
self.has_local_runtime = False
|
|
51
53
|
elif runtime_type in self.available_runtimes:
|
|
@@ -131,9 +133,10 @@ class ContainerRuntime:
|
|
|
131
133
|
|
|
132
134
|
if current_platform != required_platform:
|
|
133
135
|
_handle_warn(
|
|
134
|
-
f"
|
|
135
|
-
f"but Bedrock AgentCore requires '{required_platform}'.\n"
|
|
136
|
-
"
|
|
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: "
|
|
137
140
|
"https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/getting-started-custom.html\n"
|
|
138
141
|
)
|
|
139
142
|
|
|
@@ -150,9 +153,8 @@ class ContainerRuntime:
|
|
|
150
153
|
# If source_path provided: module path relative to source_path (Docker build context)
|
|
151
154
|
# Otherwise: module path relative to project root
|
|
152
155
|
build_context_root = Path(source_path) if source_path else output_dir
|
|
153
|
-
|
|
154
156
|
# Generate .dockerignore if it doesn't exist
|
|
155
|
-
self._ensure_dockerignore(
|
|
157
|
+
self._ensure_dockerignore(build_context_root)
|
|
156
158
|
|
|
157
159
|
# Validate module path against build context root
|
|
158
160
|
self._validate_module_path(agent_path, build_context_root)
|
|
@@ -167,11 +169,12 @@ class ContainerRuntime:
|
|
|
167
169
|
# - Otherwise: check project root (output_dir)
|
|
168
170
|
# - If explicit requirements_file provided: use that regardless
|
|
169
171
|
if source_path and not requirements_file:
|
|
170
|
-
# Check source_path directory for dependencies
|
|
171
172
|
source_dir = Path(source_path)
|
|
172
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)
|
|
173
177
|
else:
|
|
174
|
-
# Check project root for dependencies (or use explicit file)
|
|
175
178
|
deps = detect_dependencies(output_dir, explicit_file=requirements_file)
|
|
176
179
|
|
|
177
180
|
# Add logic to avoid duplicate installation
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"""Typed configuration schema for Bedrock AgentCore SDK."""
|
|
2
2
|
|
|
3
|
-
from pathlib import Path
|
|
4
3
|
from typing import Dict, List, Literal, Optional
|
|
5
4
|
|
|
6
5
|
from pydantic import BaseModel, Field, field_validator
|
|
@@ -16,7 +15,7 @@ class NetworkModeConfig(BaseModel):
|
|
|
16
15
|
class MemoryConfig(BaseModel):
|
|
17
16
|
"""Memory configuration for BedrockAgentCore."""
|
|
18
17
|
|
|
19
|
-
mode: Literal["STM_ONLY", "STM_AND_LTM"] = Field(
|
|
18
|
+
mode: Literal["STM_ONLY", "STM_AND_LTM", "NO_MEMORY"] = Field(
|
|
20
19
|
default="STM_ONLY", description="Memory mode - always has STM, optionally adds LTM"
|
|
21
20
|
)
|
|
22
21
|
memory_id: Optional[str] = Field(default=None, description="Memory resource ID")
|
|
@@ -26,11 +25,14 @@ class MemoryConfig(BaseModel):
|
|
|
26
25
|
first_invoke_memory_check_done: bool = Field(
|
|
27
26
|
default=False, description="Whether first invoke memory check has been performed"
|
|
28
27
|
)
|
|
28
|
+
was_created_by_toolkit: bool = Field(
|
|
29
|
+
default=False, description="Whether memory was created by toolkit (vs reused existing)"
|
|
30
|
+
)
|
|
29
31
|
|
|
30
32
|
@property
|
|
31
33
|
def is_enabled(self) -> bool:
|
|
32
|
-
"""Check if memory is enabled
|
|
33
|
-
return
|
|
34
|
+
"""Check if memory is enabled."""
|
|
35
|
+
return self.mode != "NO_MEMORY"
|
|
34
36
|
|
|
35
37
|
@property
|
|
36
38
|
def has_ltm(self) -> bool:
|
|
@@ -158,37 +160,6 @@ class BedrockAgentCoreAgentSchema(BaseModel):
|
|
|
158
160
|
request_header_configuration: Optional[dict] = Field(default=None, description="Request header configuration")
|
|
159
161
|
oauth_configuration: Optional[dict] = Field(default=None, description="Oauth configuration")
|
|
160
162
|
|
|
161
|
-
@field_validator("source_path")
|
|
162
|
-
@classmethod
|
|
163
|
-
def validate_source_path(cls, v: Optional[str]) -> Optional[str]:
|
|
164
|
-
"""Validate source path if provided.
|
|
165
|
-
|
|
166
|
-
Args:
|
|
167
|
-
v: Source path value
|
|
168
|
-
|
|
169
|
-
Returns:
|
|
170
|
-
Validated source path or None
|
|
171
|
-
|
|
172
|
-
Raises:
|
|
173
|
-
ValueError: If source path is invalid
|
|
174
|
-
"""
|
|
175
|
-
if v is None:
|
|
176
|
-
return v
|
|
177
|
-
|
|
178
|
-
# Convert to Path for validation
|
|
179
|
-
source_path = Path(v)
|
|
180
|
-
|
|
181
|
-
# Check if path exists
|
|
182
|
-
if not source_path.exists():
|
|
183
|
-
raise ValueError(f"Source path does not exist: {v}")
|
|
184
|
-
|
|
185
|
-
# Check if it's a directory
|
|
186
|
-
if not source_path.is_dir():
|
|
187
|
-
raise ValueError(f"Source path must be a directory: {v}")
|
|
188
|
-
|
|
189
|
-
# Return absolute path string
|
|
190
|
-
return str(source_path.resolve())
|
|
191
|
-
|
|
192
163
|
def get_authorizer_configuration(self) -> Optional[dict]:
|
|
193
164
|
"""Get the authorizer configuration."""
|
|
194
165
|
return self.authorizer_configuration
|
|
@@ -28,12 +28,6 @@ RUN uv pip install -r {{ dependencies_file }}
|
|
|
28
28
|
RUN uv pip install aws-opentelemetry-distro>=0.10.1
|
|
29
29
|
{% endif %}
|
|
30
30
|
|
|
31
|
-
# Set AWS region environment variable
|
|
32
|
-
{% if aws_region %}
|
|
33
|
-
ENV AWS_REGION={{ aws_region }}
|
|
34
|
-
ENV AWS_DEFAULT_REGION={{ aws_region }}
|
|
35
|
-
{% endif %}
|
|
36
|
-
|
|
37
31
|
# Signal that this is running in Docker for host binding logic
|
|
38
32
|
ENV DOCKER_CONTAINER=1
|
|
39
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
|