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
|
@@ -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,
|
|
@@ -41,6 +141,7 @@ def configure_bedrock_agentcore(
|
|
|
41
141
|
region: Optional[str] = None,
|
|
42
142
|
protocol: Optional[str] = None,
|
|
43
143
|
non_interactive: bool = False,
|
|
144
|
+
source_path: Optional[str] = None,
|
|
44
145
|
) -> ConfigureResult:
|
|
45
146
|
"""Configure Bedrock AgentCore application with deployment settings.
|
|
46
147
|
|
|
@@ -54,6 +155,7 @@ def configure_bedrock_agentcore(
|
|
|
54
155
|
auto_create_ecr: Whether to auto-create ECR repository
|
|
55
156
|
auto_create_execution_role: Whether to auto-create execution role if not provided
|
|
56
157
|
enable_observability: Whether to enable observability
|
|
158
|
+
memory_mode: Memory configuration mode - "NO_MEMORY", "STM_ONLY" (default), or "STM_AND_LTM"
|
|
57
159
|
requirements_file: Path to requirements file
|
|
58
160
|
authorizer_configuration: JWT authorizer configuration dictionary
|
|
59
161
|
request_header_configuration: Request header configuration dictionary
|
|
@@ -61,6 +163,7 @@ def configure_bedrock_agentcore(
|
|
|
61
163
|
region: AWS region for deployment
|
|
62
164
|
protocol: agent server protocol, must be either HTTP or MCP or A2A
|
|
63
165
|
non_interactive: Skip interactive prompts and use defaults
|
|
166
|
+
source_path: Optional path to agent source code directory
|
|
64
167
|
|
|
65
168
|
Returns:
|
|
66
169
|
ConfigureResult model with configuration details
|
|
@@ -73,10 +176,13 @@ def configure_bedrock_agentcore(
|
|
|
73
176
|
log.setLevel(logging.INFO)
|
|
74
177
|
# Log agent name at the start of configuration
|
|
75
178
|
log.info("Configuring BedrockAgentCore agent: %s", agent_name)
|
|
179
|
+
|
|
180
|
+
# Build directory is always project root for module validation and dependency detection
|
|
76
181
|
build_dir = Path.cwd()
|
|
77
182
|
|
|
78
183
|
if verbose:
|
|
79
184
|
log.debug("Build directory: %s", build_dir)
|
|
185
|
+
log.debug("Source path: %s", source_path or "None (using build directory)")
|
|
80
186
|
log.debug("Bedrock AgentCore name: %s", agent_name)
|
|
81
187
|
log.debug("Entrypoint path: %s", entrypoint_path)
|
|
82
188
|
|
|
@@ -116,32 +222,52 @@ def configure_bedrock_agentcore(
|
|
|
116
222
|
else:
|
|
117
223
|
log.debug("No execution role provided and auto-create disabled")
|
|
118
224
|
|
|
119
|
-
|
|
120
|
-
log.debug("Prompting for memory configuration")
|
|
121
|
-
|
|
225
|
+
# Pass region to ConfigurationManager so it can check for existing memories
|
|
122
226
|
config_manager = ConfigurationManager(build_dir / ".bedrock_agentcore.yaml", non_interactive)
|
|
123
227
|
|
|
124
|
-
#
|
|
125
|
-
action, value = config_manager.prompt_memory_selection()
|
|
126
|
-
|
|
228
|
+
# Handle memory configuration
|
|
127
229
|
memory_config = MemoryConfig()
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
memory_config.mode = "
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
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
|
|
137
238
|
memory_config.event_expiry_days = 30
|
|
138
239
|
memory_config.memory_name = f"{agent_name}_memory"
|
|
139
|
-
log.info("Will create new memory with mode: %s",
|
|
240
|
+
log.info("Will create new memory with mode: %s", memory_mode)
|
|
140
241
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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")
|
|
145
271
|
|
|
146
272
|
# Check for existing memory configuration from previous launch
|
|
147
273
|
config_path = build_dir / ".bedrock_agentcore.yaml"
|
|
@@ -196,24 +322,50 @@ def configure_bedrock_agentcore(
|
|
|
196
322
|
if memory_id:
|
|
197
323
|
log.debug(" Memory ID: %s", memory_id)
|
|
198
324
|
|
|
325
|
+
# Determine output directory for Dockerfile based on source_path
|
|
326
|
+
# If source_path provided: write to .bedrock_agentcore/{agent_name}/ directly
|
|
327
|
+
# Otherwise: write to project root (legacy)
|
|
328
|
+
if source_path:
|
|
329
|
+
from ...utils.runtime.config import get_agentcore_directory
|
|
330
|
+
|
|
331
|
+
dockerfile_output_dir = get_agentcore_directory(Path.cwd(), agent_name, source_path)
|
|
332
|
+
else:
|
|
333
|
+
dockerfile_output_dir = build_dir
|
|
334
|
+
|
|
335
|
+
# Generate Dockerfile in the correct location (no moving needed)
|
|
199
336
|
dockerfile_path = runtime.generate_dockerfile(
|
|
200
337
|
entrypoint_path,
|
|
201
|
-
|
|
338
|
+
dockerfile_output_dir,
|
|
202
339
|
bedrock_agentcore_name or "bedrock_agentcore",
|
|
203
340
|
region,
|
|
204
341
|
enable_observability,
|
|
205
342
|
requirements_file,
|
|
206
343
|
memory_id,
|
|
207
344
|
memory_name,
|
|
345
|
+
source_path,
|
|
208
346
|
protocol,
|
|
209
347
|
)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
if
|
|
216
|
-
|
|
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)
|
|
351
|
+
|
|
352
|
+
# Ensure .dockerignore exists at Docker build context location
|
|
353
|
+
if source_path:
|
|
354
|
+
# For source_path: .dockerignore at source directory (Docker build context)
|
|
355
|
+
source_dockerignore = Path(source_path) / ".dockerignore"
|
|
356
|
+
if not source_dockerignore.exists():
|
|
357
|
+
template_path = (
|
|
358
|
+
Path(__file__).parent.parent.parent / "utils" / "runtime" / "templates" / "dockerignore.template"
|
|
359
|
+
)
|
|
360
|
+
if template_path.exists():
|
|
361
|
+
source_dockerignore.write_text(template_path.read_text())
|
|
362
|
+
log.info("Generated .dockerignore: %s", source_dockerignore)
|
|
363
|
+
dockerignore_path = source_dockerignore
|
|
364
|
+
else:
|
|
365
|
+
# Legacy: .dockerignore at project root
|
|
366
|
+
dockerignore_path = build_dir / ".dockerignore"
|
|
367
|
+
if dockerignore_path.exists():
|
|
368
|
+
log.info("Generated .dockerignore: %s", dockerignore_path)
|
|
217
369
|
|
|
218
370
|
# Handle project configuration (named agents)
|
|
219
371
|
config_path = build_dir / ".bedrock_agentcore.yaml"
|
|
@@ -258,6 +410,7 @@ def configure_bedrock_agentcore(
|
|
|
258
410
|
entrypoint=entrypoint,
|
|
259
411
|
platform=ContainerRuntime.DEFAULT_PLATFORM,
|
|
260
412
|
container_runtime=runtime.runtime,
|
|
413
|
+
source_path=str(Path(source_path).resolve()) if source_path else None,
|
|
261
414
|
aws=AWSConfig(
|
|
262
415
|
execution_role=execution_role_arn,
|
|
263
416
|
execution_role_auto_create=execution_role_auto_create,
|
|
@@ -11,6 +11,7 @@ from ...operations.memory.manager import MemoryManager
|
|
|
11
11
|
from ...services.runtime import BedrockAgentCoreClient
|
|
12
12
|
from ...utils.runtime.config import load_config, save_config
|
|
13
13
|
from ...utils.runtime.schema import BedrockAgentCoreAgentSchema, BedrockAgentCoreConfigSchema
|
|
14
|
+
from .exceptions import RuntimeToolkitException
|
|
14
15
|
from .models import DestroyResult
|
|
15
16
|
|
|
16
17
|
log = logging.getLogger(__name__)
|
|
@@ -79,7 +80,16 @@ def destroy_bedrock_agentcore(
|
|
|
79
80
|
_destroy_codebuild_project(session, agent_config, result, dry_run)
|
|
80
81
|
|
|
81
82
|
# 5. Remove memory resource
|
|
82
|
-
|
|
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)
|
|
83
93
|
|
|
84
94
|
# 6. Remove CodeBuild IAM Role
|
|
85
95
|
_destroy_codebuild_iam_role(session, agent_config, result, dry_run)
|
|
@@ -102,7 +112,7 @@ def destroy_bedrock_agentcore(
|
|
|
102
112
|
|
|
103
113
|
except Exception as e:
|
|
104
114
|
log.error("Destroy operation failed: %s", str(e))
|
|
105
|
-
raise
|
|
115
|
+
raise RuntimeToolkitException(f"Destroy operation failed: {e}") from e
|
|
106
116
|
|
|
107
117
|
|
|
108
118
|
def _destroy_agentcore_endpoint(
|
|
@@ -129,10 +139,9 @@ def _destroy_agentcore_endpoint(
|
|
|
129
139
|
endpoint_name = endpoint_response.get("name", "DEFAULT")
|
|
130
140
|
endpoint_arn = endpoint_response.get("agentRuntimeEndpointArn")
|
|
131
141
|
|
|
132
|
-
#
|
|
142
|
+
# DEFAULT endpoint is automatically deleted when agent is deleted
|
|
133
143
|
if endpoint_name == "DEFAULT":
|
|
134
|
-
|
|
135
|
-
log.info("Skipping deletion of DEFAULT endpoint")
|
|
144
|
+
log.info("DEFAULT endpoint will be automatically deleted with agent")
|
|
136
145
|
return
|
|
137
146
|
|
|
138
147
|
if dry_run:
|
|
@@ -146,7 +155,13 @@ def _destroy_agentcore_endpoint(
|
|
|
146
155
|
result.resources_removed.append(f"AgentCore endpoint: {endpoint_arn}")
|
|
147
156
|
log.info("Deleted AgentCore endpoint: %s", endpoint_arn)
|
|
148
157
|
except ClientError as delete_error:
|
|
149
|
-
|
|
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"]:
|
|
150
165
|
result.errors.append(f"Failed to delete endpoint {endpoint_arn}: {delete_error}")
|
|
151
166
|
log.error("Failed to delete endpoint: %s", delete_error)
|
|
152
167
|
else:
|
|
@@ -368,7 +383,9 @@ def _destroy_codebuild_project(
|
|
|
368
383
|
"""Remove CodeBuild project for this agent."""
|
|
369
384
|
try:
|
|
370
385
|
codebuild_client = session.client("codebuild", region_name=agent_config.aws.region)
|
|
371
|
-
|
|
386
|
+
from ...services.ecr import sanitize_ecr_repo_name
|
|
387
|
+
|
|
388
|
+
project_name = f"bedrock-agentcore-{sanitize_ecr_repo_name(agent_config.name)}-builder"
|
|
372
389
|
|
|
373
390
|
if dry_run:
|
|
374
391
|
result.resources_removed.append(f"CodeBuild project: {project_name} (DRY RUN)")
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Exceptions for the Bedrock AgentCore Runtime module."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class RuntimeException(Exception):
|
|
7
|
+
"""Base exception for all Runtime SDK errors."""
|
|
8
|
+
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RuntimeToolkitException(RuntimeException):
|
|
13
|
+
"""Raised when runtime operations fail with resource tracking."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, message: str, created_resources: Optional[List[str]] = None):
|
|
16
|
+
"""Initialize RuntimeToolkitException with optional resource tracking.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
message: Error message
|
|
20
|
+
created_resources: List of resources created before failure
|
|
21
|
+
"""
|
|
22
|
+
self.created_resources = created_resources or []
|
|
23
|
+
if created_resources:
|
|
24
|
+
full_message = f"{message}. Resources created: {created_resources}"
|
|
25
|
+
else:
|
|
26
|
+
full_message = message
|
|
27
|
+
super().__init__(full_message)
|
|
@@ -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
|
)
|
|
@@ -19,6 +19,7 @@ from ...utils.runtime.container import ContainerRuntime
|
|
|
19
19
|
from ...utils.runtime.logs import get_genai_observability_url
|
|
20
20
|
from ...utils.runtime.schema import BedrockAgentCoreAgentSchema, BedrockAgentCoreConfigSchema
|
|
21
21
|
from .create_role import get_or_create_runtime_execution_role
|
|
22
|
+
from .exceptions import RuntimeToolkitException
|
|
22
23
|
from .models import LaunchResult
|
|
23
24
|
|
|
24
25
|
log = logging.getLogger(__name__)
|
|
@@ -139,7 +140,13 @@ def _ensure_memory_for_agent(
|
|
|
139
140
|
"""Ensure memory resource exists for agent. Returns memory_id or None.
|
|
140
141
|
|
|
141
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.
|
|
142
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
|
+
|
|
143
150
|
# If memory already exists, return it
|
|
144
151
|
if agent_config.memory and agent_config.memory.memory_id:
|
|
145
152
|
log.info("Using existing memory: %s", agent_config.memory.memory_id)
|
|
@@ -157,14 +164,16 @@ def _ensure_memory_for_agent(
|
|
|
157
164
|
memory_manager = MemoryManager(region_name=agent_config.aws.region)
|
|
158
165
|
memory_name = f"{agent_name}_mem" # Short name under 48 char limit
|
|
159
166
|
|
|
160
|
-
# Check if memory already exists
|
|
167
|
+
# Check if memory already exists in cloud
|
|
161
168
|
existing_memory = None
|
|
162
169
|
try:
|
|
163
170
|
memories = memory_manager.list_memories()
|
|
164
171
|
for m in memories:
|
|
165
172
|
if m.id.startswith(memory_name):
|
|
166
173
|
existing_memory = memory_manager.get_memory(m.id)
|
|
167
|
-
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
|
|
168
177
|
break
|
|
169
178
|
except Exception as e:
|
|
170
179
|
log.debug("Error checking for existing memory: %s", e)
|
|
@@ -250,9 +259,16 @@ def _ensure_memory_for_agent(
|
|
|
250
259
|
event_expiry_days=agent_config.memory.event_expiry_days or 30,
|
|
251
260
|
memory_execution_role_arn=None,
|
|
252
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
|
+
|
|
253
269
|
log.info("✅ New memory created: %s (provisioning in background)", memory.id)
|
|
254
270
|
|
|
255
|
-
# Save memory configuration
|
|
271
|
+
# Save memory configuration (preserving was_created_by_toolkit flag)
|
|
256
272
|
agent_config.memory.memory_id = memory.id
|
|
257
273
|
agent_config.memory.memory_arn = memory.arn
|
|
258
274
|
agent_config.memory.memory_name = memory_name
|
|
@@ -479,14 +495,24 @@ def launch_bedrock_agentcore(
|
|
|
479
495
|
"💡 For local builds, please install Docker, Finch, or Podman"
|
|
480
496
|
)
|
|
481
497
|
|
|
482
|
-
# Get build context -
|
|
483
|
-
build_dir = config_path.parent
|
|
498
|
+
# Get build context - use source_path if configured, otherwise use project root
|
|
499
|
+
build_dir = Path(agent_config.source_path) if agent_config.source_path else config_path.parent
|
|
500
|
+
log.info("Using build directory: %s", build_dir)
|
|
484
501
|
|
|
485
502
|
bedrock_agentcore_name = agent_config.name
|
|
486
503
|
tag = f"bedrock_agentcore-{bedrock_agentcore_name}:latest"
|
|
487
504
|
|
|
488
505
|
# Step 1: Build Docker image (only if we need it)
|
|
489
|
-
|
|
506
|
+
# When using source_path, Dockerfile is in .bedrock_agentcore/{agent_name}/ directory
|
|
507
|
+
from ...utils.runtime.config import get_agentcore_directory
|
|
508
|
+
|
|
509
|
+
dockerfile_dir = get_agentcore_directory(config_path.parent, agent_config.name, agent_config.source_path)
|
|
510
|
+
dockerfile_path = dockerfile_dir / "Dockerfile"
|
|
511
|
+
|
|
512
|
+
if not dockerfile_path.exists():
|
|
513
|
+
raise RuntimeError(f"Dockerfile not found at {dockerfile_path}. Please run 'agentcore configure' first.")
|
|
514
|
+
|
|
515
|
+
success, output = runtime.build(build_dir, tag, dockerfile_path=dockerfile_path)
|
|
490
516
|
if not success:
|
|
491
517
|
error_lines = output[-10:] if len(output) > 10 else output
|
|
492
518
|
error_message = " ".join(error_lines)
|
|
@@ -573,53 +599,82 @@ def _execute_codebuild_workflow(
|
|
|
573
599
|
agent_config.aws.account,
|
|
574
600
|
agent_config.aws.region,
|
|
575
601
|
)
|
|
576
|
-
# Validate configuration
|
|
577
|
-
errors = agent_config.validate(for_local=False)
|
|
578
|
-
if errors:
|
|
579
|
-
raise ValueError(f"Invalid configuration: {', '.join(errors)}")
|
|
580
602
|
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
raise ValueError("Region not found in configuration")
|
|
603
|
+
# Track created resources for error context
|
|
604
|
+
created_resources = []
|
|
584
605
|
|
|
585
|
-
|
|
586
|
-
|
|
606
|
+
try:
|
|
607
|
+
# Validate configuration
|
|
608
|
+
errors = agent_config.validate(for_local=False)
|
|
609
|
+
if errors:
|
|
610
|
+
raise ValueError(f"Invalid configuration: {', '.join(errors)}")
|
|
611
|
+
|
|
612
|
+
region = agent_config.aws.region
|
|
613
|
+
if not region:
|
|
614
|
+
raise ValueError("Region not found in configuration")
|
|
615
|
+
|
|
616
|
+
session = boto3.Session(region_name=region)
|
|
617
|
+
account_id = agent_config.aws.account # Use existing account from config
|
|
618
|
+
|
|
619
|
+
# Setup AWS resources
|
|
620
|
+
log.info("Setting up AWS resources (ECR repository%s)...", "" if ecr_only else ", execution roles")
|
|
621
|
+
ecr_uri = _ensure_ecr_repository(agent_config, project_config, config_path, agent_name, region)
|
|
622
|
+
if ecr_uri:
|
|
623
|
+
created_resources.append(f"ECR Repository: {ecr_uri}")
|
|
624
|
+
ecr_repository_arn = f"arn:aws:ecr:{region}:{account_id}:repository/{ecr_uri.split('/')[-1]}"
|
|
625
|
+
|
|
626
|
+
# Setup execution role only if not ECR-only mode
|
|
627
|
+
if not ecr_only:
|
|
628
|
+
_ensure_execution_role(agent_config, project_config, config_path, agent_name, region, account_id)
|
|
629
|
+
if agent_config.aws.execution_role:
|
|
630
|
+
created_resources.append(f"Runtime Execution Role: {agent_config.aws.execution_role}")
|
|
631
|
+
|
|
632
|
+
# Prepare CodeBuild
|
|
633
|
+
log.info("Preparing CodeBuild project and uploading source...")
|
|
634
|
+
codebuild_service = CodeBuildService(session)
|
|
635
|
+
|
|
636
|
+
# Use cached CodeBuild role from config if available
|
|
637
|
+
if hasattr(agent_config, "codebuild") and agent_config.codebuild.execution_role:
|
|
638
|
+
log.info("Using CodeBuild role from config: %s", agent_config.codebuild.execution_role)
|
|
639
|
+
codebuild_execution_role = agent_config.codebuild.execution_role
|
|
640
|
+
else:
|
|
641
|
+
codebuild_execution_role = codebuild_service.create_codebuild_execution_role(
|
|
642
|
+
account_id=account_id, ecr_repository_arn=ecr_repository_arn, agent_name=agent_name
|
|
643
|
+
)
|
|
644
|
+
if codebuild_execution_role:
|
|
645
|
+
created_resources.append(f"CodeBuild Execution Role: {codebuild_execution_role}")
|
|
587
646
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
ecr_uri = _ensure_ecr_repository(agent_config, project_config, config_path, agent_name, region)
|
|
591
|
-
ecr_repository_arn = f"arn:aws:ecr:{region}:{account_id}:repository/{ecr_uri.split('/')[-1]}"
|
|
647
|
+
# Get source directory - use source_path if configured, otherwise use current directory
|
|
648
|
+
source_dir = str(Path(agent_config.source_path)) if agent_config.source_path else "."
|
|
592
649
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
_ensure_execution_role(agent_config, project_config, config_path, agent_name, region, account_id)
|
|
650
|
+
# Get Dockerfile directory - use agentcore directory if source_path provided
|
|
651
|
+
from ...utils.runtime.config import get_agentcore_directory
|
|
596
652
|
|
|
597
|
-
|
|
598
|
-
log.info("Preparing CodeBuild project and uploading source...")
|
|
599
|
-
codebuild_service = CodeBuildService(session)
|
|
653
|
+
dockerfile_dir = get_agentcore_directory(config_path.parent, agent_name, agent_config.source_path)
|
|
600
654
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
log.info("Using CodeBuild role from config: %s", agent_config.codebuild.execution_role)
|
|
604
|
-
codebuild_execution_role = agent_config.codebuild.execution_role
|
|
605
|
-
else:
|
|
606
|
-
codebuild_execution_role = codebuild_service.create_codebuild_execution_role(
|
|
607
|
-
account_id=account_id, ecr_repository_arn=ecr_repository_arn, agent_name=agent_name
|
|
655
|
+
source_location = codebuild_service.upload_source(
|
|
656
|
+
agent_name=agent_name, source_dir=source_dir, dockerfile_dir=str(dockerfile_dir)
|
|
608
657
|
)
|
|
609
658
|
|
|
610
|
-
|
|
659
|
+
# Use cached project name from config if available
|
|
660
|
+
if hasattr(agent_config, "codebuild") and agent_config.codebuild.project_name:
|
|
661
|
+
log.info("Using CodeBuild project from config: %s", agent_config.codebuild.project_name)
|
|
662
|
+
project_name = agent_config.codebuild.project_name
|
|
663
|
+
else:
|
|
664
|
+
project_name = codebuild_service.create_or_update_project(
|
|
665
|
+
agent_name=agent_name,
|
|
666
|
+
ecr_repository_uri=ecr_uri,
|
|
667
|
+
execution_role=codebuild_execution_role,
|
|
668
|
+
source_location=source_location,
|
|
669
|
+
)
|
|
670
|
+
if project_name:
|
|
671
|
+
created_resources.append(f"CodeBuild Project: {project_name}")
|
|
611
672
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
project_name = codebuild_service.create_or_update_project(
|
|
618
|
-
agent_name=agent_name,
|
|
619
|
-
ecr_repository_uri=ecr_uri,
|
|
620
|
-
execution_role=codebuild_execution_role,
|
|
621
|
-
source_location=source_location,
|
|
622
|
-
)
|
|
673
|
+
except Exception as e:
|
|
674
|
+
if created_resources:
|
|
675
|
+
log.error("Launch failed after creating the following resources: %s. Error: %s", created_resources, str(e))
|
|
676
|
+
raise RuntimeToolkitException("Launch failed", created_resources) from e
|
|
677
|
+
raise
|
|
623
678
|
|
|
624
679
|
# Execute CodeBuild
|
|
625
680
|
log.info("Starting CodeBuild build (this may take several minutes)...")
|