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
|
@@ -20,7 +20,7 @@ def _handle_error(message: str, exception: Optional[Exception] = None) -> NoRetu
|
|
|
20
20
|
|
|
21
21
|
def _handle_warn(message: str) -> None:
|
|
22
22
|
"""Handle errors with consistent formatting and exit."""
|
|
23
|
-
console.print(f"⚠️
|
|
23
|
+
console.print(f"⚠️ {message}", new_line_start=True, style="yellow")
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
def _print_success(message: str) -> None:
|
|
@@ -15,13 +15,16 @@ from rich.syntax import Syntax
|
|
|
15
15
|
from ...operations.runtime import (
|
|
16
16
|
configure_bedrock_agentcore,
|
|
17
17
|
destroy_bedrock_agentcore,
|
|
18
|
+
detect_entrypoint,
|
|
19
|
+
detect_requirements,
|
|
20
|
+
get_relative_path,
|
|
18
21
|
get_status,
|
|
22
|
+
infer_agent_name,
|
|
19
23
|
invoke_bedrock_agentcore,
|
|
20
24
|
launch_bedrock_agentcore,
|
|
21
25
|
validate_agent_name,
|
|
22
26
|
)
|
|
23
27
|
from ...utils.runtime.config import load_config
|
|
24
|
-
from ...utils.runtime.entrypoint import parse_entrypoint
|
|
25
28
|
from ...utils.runtime.logs import get_agent_log_paths, get_aws_tail_commands, get_genai_observability_url
|
|
26
29
|
from ..common import _handle_error, _print_success, console
|
|
27
30
|
from .configuration_manager import ConfigurationManager
|
|
@@ -47,63 +50,102 @@ def _show_configuration_not_found_panel():
|
|
|
47
50
|
|
|
48
51
|
|
|
49
52
|
def _validate_requirements_file(file_path: str) -> str:
|
|
50
|
-
"""Validate requirements file and return the path."""
|
|
53
|
+
"""Validate requirements file and return the absolute path."""
|
|
51
54
|
from ...utils.runtime.entrypoint import validate_requirements_file
|
|
52
55
|
|
|
53
56
|
try:
|
|
54
57
|
deps = validate_requirements_file(Path.cwd(), file_path)
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
rel_path = get_relative_path(Path(deps.resolved_path))
|
|
59
|
+
_print_success(f"Using requirements file: [dim]{rel_path}[/dim]")
|
|
60
|
+
# Return absolute path for consistency with entrypoint handling
|
|
61
|
+
return str(Path(deps.resolved_path).resolve())
|
|
57
62
|
except (FileNotFoundError, ValueError) as e:
|
|
58
63
|
_handle_error(str(e), e)
|
|
59
64
|
|
|
60
65
|
|
|
61
|
-
def _prompt_for_requirements_file(prompt_text: str, default: str = "") -> Optional[str]:
|
|
62
|
-
"""Prompt user for requirements file path with validation.
|
|
63
|
-
|
|
66
|
+
def _prompt_for_requirements_file(prompt_text: str, source_path: str, default: str = "") -> Optional[str]:
|
|
67
|
+
"""Prompt user for requirements file path with validation.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
prompt_text: Prompt message to display
|
|
71
|
+
source_path: Source directory path for validation
|
|
72
|
+
default: Default path to pre-populate
|
|
73
|
+
"""
|
|
74
|
+
# Pre-populate with relative source directory path if no default provided
|
|
75
|
+
if not default:
|
|
76
|
+
rel_source = get_relative_path(Path(source_path))
|
|
77
|
+
default = f"{rel_source}/"
|
|
78
|
+
|
|
79
|
+
# Use PathCompleter without filter - allow navigation anywhere
|
|
80
|
+
response = prompt(prompt_text, completer=PathCompleter(), complete_while_typing=True, default=default)
|
|
64
81
|
|
|
65
82
|
if response.strip():
|
|
83
|
+
# Validate file exists and is in source directory
|
|
84
|
+
req_file = Path(response.strip()).resolve()
|
|
85
|
+
source_dir = Path(source_path).resolve()
|
|
86
|
+
|
|
87
|
+
# Check if requirements file is within source directory
|
|
88
|
+
try:
|
|
89
|
+
if not req_file.is_relative_to(source_dir):
|
|
90
|
+
rel_source = get_relative_path(source_dir)
|
|
91
|
+
console.print(f"[red]Error: Requirements file must be in source directory: {rel_source}[/red]")
|
|
92
|
+
return _prompt_for_requirements_file(prompt_text, source_path, default)
|
|
93
|
+
except (ValueError, AttributeError):
|
|
94
|
+
# is_relative_to not available or other error - skip validation
|
|
95
|
+
pass
|
|
96
|
+
|
|
66
97
|
return _validate_requirements_file(response.strip())
|
|
67
98
|
|
|
68
99
|
return None
|
|
69
100
|
|
|
70
101
|
|
|
71
|
-
def _handle_requirements_file_display(
|
|
72
|
-
|
|
73
|
-
|
|
102
|
+
def _handle_requirements_file_display(
|
|
103
|
+
requirements_file: Optional[str], non_interactive: bool = False, source_path: Optional[str] = None
|
|
104
|
+
) -> Optional[str]:
|
|
105
|
+
"""Handle requirements file with display logic for CLI.
|
|
74
106
|
|
|
107
|
+
Args:
|
|
108
|
+
requirements_file: Explicit requirements file path
|
|
109
|
+
non_interactive: Whether to skip interactive prompts
|
|
110
|
+
source_path: Optional source code directory
|
|
111
|
+
"""
|
|
75
112
|
if requirements_file:
|
|
76
113
|
# User provided file - validate and show confirmation
|
|
77
114
|
return _validate_requirements_file(requirements_file)
|
|
78
115
|
|
|
116
|
+
# Use operations layer for detection - source_path is always provided
|
|
117
|
+
deps = detect_requirements(Path(source_path))
|
|
118
|
+
|
|
79
119
|
if non_interactive:
|
|
80
120
|
# Auto-detection for non-interactive mode
|
|
81
|
-
deps = detect_dependencies(Path.cwd())
|
|
82
121
|
if deps.found:
|
|
83
|
-
|
|
122
|
+
rel_deps_path = get_relative_path(Path(deps.resolved_path))
|
|
123
|
+
_print_success(f"Using detected requirements file: [cyan]{rel_deps_path}[/cyan]")
|
|
84
124
|
return None # Use detected file
|
|
85
125
|
else:
|
|
86
126
|
_handle_error("No requirements file specified and none found automatically")
|
|
87
127
|
|
|
88
128
|
# Auto-detection with interactive prompt
|
|
89
|
-
deps = detect_dependencies(Path.cwd())
|
|
90
|
-
|
|
91
129
|
if deps.found:
|
|
92
|
-
|
|
130
|
+
rel_deps_path = get_relative_path(Path(deps.resolved_path))
|
|
131
|
+
|
|
132
|
+
console.print(f"\n🔍 [cyan]Detected dependency file:[/cyan] [bold]{rel_deps_path}[/bold]")
|
|
93
133
|
console.print("[dim]Press Enter to use this file, or type a different path (use Tab for autocomplete):[/dim]")
|
|
94
134
|
|
|
95
|
-
result = _prompt_for_requirements_file(
|
|
135
|
+
result = _prompt_for_requirements_file(
|
|
136
|
+
"Path or Press Enter to use detected dependency file: ", source_path=source_path, default=rel_deps_path
|
|
137
|
+
)
|
|
96
138
|
|
|
97
139
|
if result is None:
|
|
98
140
|
# Use detected file
|
|
99
|
-
_print_success(f"Using detected file: [
|
|
141
|
+
_print_success(f"Using detected requirements file: [cyan]{rel_deps_path}[/cyan]")
|
|
100
142
|
|
|
101
143
|
return result
|
|
102
144
|
else:
|
|
103
145
|
console.print("\n[yellow]⚠️ No dependency file found (requirements.txt or pyproject.toml)[/yellow]")
|
|
104
146
|
console.print("[dim]Enter path to requirements file (use Tab for autocomplete), or press Enter to skip:[/dim]")
|
|
105
147
|
|
|
106
|
-
result = _prompt_for_requirements_file("Path: ")
|
|
148
|
+
result = _prompt_for_requirements_file("Path: ", source_path=source_path)
|
|
107
149
|
|
|
108
150
|
if result is None:
|
|
109
151
|
_handle_error("No requirements file specified and none found automatically")
|
|
@@ -111,6 +153,29 @@ def _handle_requirements_file_display(requirements_file: Optional[str], non_inte
|
|
|
111
153
|
return result
|
|
112
154
|
|
|
113
155
|
|
|
156
|
+
def _detect_entrypoint_in_source(source_path: str, non_interactive: bool = False) -> str:
|
|
157
|
+
"""Detect entrypoint file in source directory with CLI display."""
|
|
158
|
+
source_dir = Path(source_path)
|
|
159
|
+
|
|
160
|
+
# Use operations layer for detection
|
|
161
|
+
detected = detect_entrypoint(source_dir)
|
|
162
|
+
|
|
163
|
+
if not detected:
|
|
164
|
+
# No fallback prompt - fail with clear error message
|
|
165
|
+
rel_source = get_relative_path(source_dir)
|
|
166
|
+
_handle_error(
|
|
167
|
+
f"No entrypoint file found in {rel_source}\n"
|
|
168
|
+
f"Expected one of: main.py, agent.py, app.py, __main__.py\n"
|
|
169
|
+
f"Please specify full file path (e.g., {rel_source}/your_agent.py)"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# Show detection and confirm
|
|
173
|
+
rel_entrypoint = get_relative_path(detected)
|
|
174
|
+
|
|
175
|
+
_print_success(f"Using entrypoint file: [cyan]{rel_entrypoint}[/cyan]")
|
|
176
|
+
return str(detected)
|
|
177
|
+
|
|
178
|
+
|
|
114
179
|
# Define options at module level to avoid B008
|
|
115
180
|
ENV_OPTION = typer.Option(None, "--env", "-env", help="Environment variables for local mode (format: KEY=VALUE)")
|
|
116
181
|
|
|
@@ -164,7 +229,12 @@ def set_default(name: str = typer.Argument(...)):
|
|
|
164
229
|
@configure_app.callback(invoke_without_command=True)
|
|
165
230
|
def configure(
|
|
166
231
|
ctx: typer.Context,
|
|
167
|
-
entrypoint: Optional[str] = typer.Option(
|
|
232
|
+
entrypoint: Optional[str] = typer.Option(
|
|
233
|
+
None,
|
|
234
|
+
"--entrypoint",
|
|
235
|
+
"-e",
|
|
236
|
+
help="Entry point: file path (e.g., agent.py) or directory path (auto-detects main.py, agent.py, app.py)",
|
|
237
|
+
),
|
|
168
238
|
agent_name: Optional[str] = typer.Option(None, "--name", "-n"),
|
|
169
239
|
execution_role: Optional[str] = typer.Option(None, "--execution-role", "-er"),
|
|
170
240
|
code_build_execution_role: Optional[str] = typer.Option(None, "--code-build-execution-role", "-cber"),
|
|
@@ -174,6 +244,7 @@ def configure(
|
|
|
174
244
|
None, "--requirements-file", "-rf", help="Path to requirements file"
|
|
175
245
|
),
|
|
176
246
|
disable_otel: bool = typer.Option(False, "--disable-otel", "-do", help="Disable OpenTelemetry"),
|
|
247
|
+
disable_memory: bool = typer.Option(False, "--disable-memory", "-dm", help="Disable memory"),
|
|
177
248
|
authorizer_config: Optional[str] = typer.Option(
|
|
178
249
|
None, "--authorizer-config", "-ac", help="OAuth authorizer configuration as JSON string"
|
|
179
250
|
),
|
|
@@ -191,33 +262,74 @@ def configure(
|
|
|
191
262
|
False, "--non-interactive", "-ni", help="Skip prompts; use defaults unless overridden"
|
|
192
263
|
),
|
|
193
264
|
):
|
|
194
|
-
"""Configure a Bedrock AgentCore agent
|
|
265
|
+
"""Configure a Bedrock AgentCore agent interactively or with parameters.
|
|
266
|
+
|
|
267
|
+
Examples:
|
|
268
|
+
agentcore configure # Fully interactive (current directory)
|
|
269
|
+
agentcore configure --entrypoint writer/ # Directory (auto-detect entrypoint)
|
|
270
|
+
agentcore configure --entrypoint agent.py # File (use as entrypoint)
|
|
271
|
+
"""
|
|
195
272
|
if ctx.invoked_subcommand is not None:
|
|
196
273
|
return
|
|
197
274
|
|
|
198
|
-
if not entrypoint:
|
|
199
|
-
_handle_error("--entrypoint is required")
|
|
200
|
-
|
|
201
275
|
if protocol and protocol.upper() not in ["HTTP", "MCP", "A2A"]:
|
|
202
276
|
_handle_error("Error: --protocol must be either HTTP or MCP or A2A")
|
|
203
277
|
|
|
204
278
|
console.print("[cyan]Configuring Bedrock AgentCore...[/cyan]")
|
|
205
|
-
try:
|
|
206
|
-
_, file_name = parse_entrypoint(entrypoint)
|
|
207
|
-
agent_name = agent_name or file_name
|
|
208
|
-
|
|
209
|
-
valid, error = validate_agent_name(agent_name)
|
|
210
|
-
if not valid:
|
|
211
|
-
_handle_error(error)
|
|
212
|
-
|
|
213
|
-
console.print(f"[dim]Agent name: {agent_name}[/dim]")
|
|
214
|
-
except ValueError as e:
|
|
215
|
-
_handle_error(f"Error: {e}", e)
|
|
216
279
|
|
|
217
|
-
# Create configuration manager for
|
|
280
|
+
# Create configuration manager early for consistent prompting
|
|
218
281
|
config_path = Path.cwd() / ".bedrock_agentcore.yaml"
|
|
219
282
|
config_manager = ConfigurationManager(config_path, non_interactive)
|
|
220
283
|
|
|
284
|
+
# Interactive entrypoint selection
|
|
285
|
+
if not entrypoint:
|
|
286
|
+
if non_interactive:
|
|
287
|
+
entrypoint_input = "."
|
|
288
|
+
else:
|
|
289
|
+
console.print("\n📂 [cyan]Entrypoint Selection[/cyan]")
|
|
290
|
+
console.print("[dim]Specify the entry point (use Tab for autocomplete):[/dim]")
|
|
291
|
+
console.print("[dim] • File path: weather/agent.py[/dim]")
|
|
292
|
+
console.print("[dim] • Directory: weather/ (auto-detects main.py, agent.py, app.py)[/dim]")
|
|
293
|
+
console.print("[dim] • Current directory: press Enter[/dim]")
|
|
294
|
+
|
|
295
|
+
entrypoint_input = (
|
|
296
|
+
prompt("Entrypoint: ", completer=PathCompleter(), complete_while_typing=True, default="").strip() or "."
|
|
297
|
+
)
|
|
298
|
+
else:
|
|
299
|
+
entrypoint_input = entrypoint
|
|
300
|
+
|
|
301
|
+
# Resolve the entrypoint_input (handles both file and directory)
|
|
302
|
+
entrypoint_path = Path(entrypoint_input).resolve()
|
|
303
|
+
|
|
304
|
+
if entrypoint_path.is_file():
|
|
305
|
+
# It's a file - use directly as entrypoint
|
|
306
|
+
entrypoint = str(entrypoint_path)
|
|
307
|
+
source_path = str(entrypoint_path.parent)
|
|
308
|
+
if not non_interactive:
|
|
309
|
+
rel_path = get_relative_path(entrypoint_path)
|
|
310
|
+
_print_success(f"Using file: {rel_path}")
|
|
311
|
+
elif entrypoint_path.is_dir():
|
|
312
|
+
# It's a directory - detect entrypoint within it
|
|
313
|
+
source_path = str(entrypoint_path)
|
|
314
|
+
entrypoint = _detect_entrypoint_in_source(source_path, non_interactive)
|
|
315
|
+
else:
|
|
316
|
+
_handle_error(f"Path not found: {entrypoint_input}")
|
|
317
|
+
|
|
318
|
+
# Process agent name
|
|
319
|
+
entrypoint_path = Path(entrypoint)
|
|
320
|
+
|
|
321
|
+
# Infer agent name from full entrypoint path (e.g., agents/writer/main.py -> agents_writer_main)
|
|
322
|
+
if not agent_name:
|
|
323
|
+
suggested_name = infer_agent_name(entrypoint_path)
|
|
324
|
+
agent_name = config_manager.prompt_agent_name(suggested_name)
|
|
325
|
+
|
|
326
|
+
valid, error = validate_agent_name(agent_name)
|
|
327
|
+
if not valid:
|
|
328
|
+
_handle_error(error)
|
|
329
|
+
|
|
330
|
+
# Handle dependency file selection with simplified logic
|
|
331
|
+
final_requirements_file = _handle_requirements_file_display(requirements_file, non_interactive, source_path)
|
|
332
|
+
|
|
221
333
|
# Interactive prompts for missing values - clean and elegant
|
|
222
334
|
if not execution_role:
|
|
223
335
|
execution_role = config_manager.prompt_execution_role()
|
|
@@ -236,9 +348,6 @@ def configure(
|
|
|
236
348
|
auto_create_ecr = False
|
|
237
349
|
_print_success(f"Using existing ECR repository: [dim]{ecr_repository}[/dim]")
|
|
238
350
|
|
|
239
|
-
# Handle dependency file selection with simplified logic
|
|
240
|
-
final_requirements_file = _handle_requirements_file_display(requirements_file, non_interactive)
|
|
241
|
-
|
|
242
351
|
# Handle OAuth authorization configuration
|
|
243
352
|
oauth_config = None
|
|
244
353
|
if authorizer_config:
|
|
@@ -264,6 +373,11 @@ def configure(
|
|
|
264
373
|
else:
|
|
265
374
|
request_header_config = config_manager.prompt_request_header_allowlist()
|
|
266
375
|
|
|
376
|
+
if disable_memory:
|
|
377
|
+
memory_mode_value = "NO_MEMORY"
|
|
378
|
+
else:
|
|
379
|
+
memory_mode_value = "STM_ONLY"
|
|
380
|
+
|
|
267
381
|
try:
|
|
268
382
|
result = configure_bedrock_agentcore(
|
|
269
383
|
agent_name=agent_name,
|
|
@@ -274,6 +388,7 @@ def configure(
|
|
|
274
388
|
container_runtime=container_runtime,
|
|
275
389
|
auto_create_ecr=auto_create_ecr,
|
|
276
390
|
enable_observability=not disable_otel,
|
|
391
|
+
memory_mode=memory_mode_value,
|
|
277
392
|
requirements_file=final_requirements_file,
|
|
278
393
|
authorizer_configuration=oauth_config,
|
|
279
394
|
request_header_configuration=request_header_config,
|
|
@@ -281,6 +396,7 @@ def configure(
|
|
|
281
396
|
region=region,
|
|
282
397
|
protocol=protocol.upper() if protocol else None,
|
|
283
398
|
non_interactive=non_interactive,
|
|
399
|
+
source_path=source_path,
|
|
284
400
|
)
|
|
285
401
|
|
|
286
402
|
# Prepare authorization info for summary
|
|
@@ -294,22 +410,26 @@ def configure(
|
|
|
294
410
|
headers = request_header_config.get("requestHeaderAllowlist", [])
|
|
295
411
|
headers_info = f"Request Headers Allowlist: [dim]{len(headers)} headers configured[/dim]\n"
|
|
296
412
|
|
|
413
|
+
execution_role_display = "Auto-create" if not result.execution_role else result.execution_role
|
|
414
|
+
memory_info = "Short-term memory (30-day retention)"
|
|
415
|
+
if disable_memory:
|
|
416
|
+
memory_info = "Disabled"
|
|
417
|
+
|
|
297
418
|
console.print(
|
|
298
419
|
Panel(
|
|
299
|
-
f"[
|
|
300
|
-
f"[bold]Agent Details:[/bold]\n"
|
|
420
|
+
f"[bold]Agent Details[/bold]\n"
|
|
301
421
|
f"Agent Name: [cyan]{agent_name}[/cyan]\n"
|
|
302
422
|
f"Runtime: [cyan]{result.runtime}[/cyan]\n"
|
|
303
423
|
f"Region: [cyan]{result.region}[/cyan]\n"
|
|
304
|
-
f"Account: [
|
|
305
|
-
f"[bold]Configuration
|
|
306
|
-
f"Execution Role: [
|
|
307
|
-
f"ECR Repository: [
|
|
424
|
+
f"Account: [cyan]{result.account_id}[/cyan]\n\n"
|
|
425
|
+
f"[bold]Configuration[/bold]\n"
|
|
426
|
+
f"Execution Role: [cyan]{execution_role_display}[/cyan]\n"
|
|
427
|
+
f"ECR Repository: [cyan]"
|
|
308
428
|
f"{'Auto-create' if result.auto_create_ecr else result.ecr_repository or 'N/A'}"
|
|
309
|
-
f"[/
|
|
310
|
-
f"Authorization: [
|
|
429
|
+
f"[/cyan]\n"
|
|
430
|
+
f"Authorization: [cyan]{auth_info}[/cyan]\n\n"
|
|
311
431
|
f"{headers_info}\n"
|
|
312
|
-
f"Memory: [
|
|
432
|
+
f"Memory: [cyan]{memory_info}[/cyan]\n\n"
|
|
313
433
|
f"📄 Config saved to: [dim]{result.config_path}[/dim]\n\n"
|
|
314
434
|
f"[bold]Next Steps:[/bold]\n"
|
|
315
435
|
f" [cyan]agentcore launch[/cyan]",
|
|
@@ -473,7 +593,6 @@ def launch(
|
|
|
473
593
|
region = agent_config.aws.region if agent_config else "us-east-1"
|
|
474
594
|
|
|
475
595
|
deploy_panel = (
|
|
476
|
-
f"✅ [green]CodeBuild Deployment Successful![/green]\n\n"
|
|
477
596
|
f"[bold]Agent Details:[/bold]\n"
|
|
478
597
|
f"Agent Name: [cyan]{agent_name}[/cyan]\n"
|
|
479
598
|
f"Agent ARN: [cyan]{result.agent_arn}[/cyan]\n"
|
|
@@ -513,15 +632,12 @@ def launch(
|
|
|
513
632
|
|
|
514
633
|
if local_build:
|
|
515
634
|
title = "Local Build Success"
|
|
516
|
-
deployment_type = "✅ [green]Local Build Deployment Successful![/green]"
|
|
517
635
|
icon = "🔧"
|
|
518
636
|
else:
|
|
519
637
|
title = "Deployment Success"
|
|
520
|
-
deployment_type = "✅ [green]Deployment Successful![/green]"
|
|
521
638
|
icon = "🚀"
|
|
522
639
|
|
|
523
640
|
deploy_panel = (
|
|
524
|
-
f"{deployment_type}\n\n"
|
|
525
641
|
f"[bold]Agent Details:[/bold]\n"
|
|
526
642
|
f"Agent Name: [cyan]{agent_name}[/cyan]\n"
|
|
527
643
|
f"Agent ARN: [cyan]{result.agent_arn}[/cyan]\n"
|
|
@@ -10,18 +10,43 @@ from ..common import _handle_error, _print_success, _prompt_with_default, consol
|
|
|
10
10
|
class ConfigurationManager:
|
|
11
11
|
"""Manages interactive configuration prompts with existing configuration defaults."""
|
|
12
12
|
|
|
13
|
-
def __init__(self, config_path: Path, non_interactive: bool = False):
|
|
13
|
+
def __init__(self, config_path: Path, non_interactive: bool = False, region: Optional[str] = None):
|
|
14
14
|
"""Initialize the ConfigPrompt with a configuration path.
|
|
15
15
|
|
|
16
16
|
Args:
|
|
17
17
|
config_path: Path to the configuration file
|
|
18
18
|
non_interactive: If True, use defaults without prompting
|
|
19
|
+
region: AWS region for checking existing memories (optional, from configure operation)
|
|
19
20
|
"""
|
|
20
21
|
from ...utils.runtime.config import load_config_if_exists
|
|
21
22
|
|
|
22
23
|
project_config = load_config_if_exists(config_path)
|
|
23
24
|
self.existing_config = project_config.get_agent_config() if project_config else None
|
|
24
25
|
self.non_interactive = non_interactive
|
|
26
|
+
self.region = region
|
|
27
|
+
|
|
28
|
+
def prompt_agent_name(self, suggested_name: str) -> str:
|
|
29
|
+
"""Prompt for agent name with a suggested default.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
suggested_name: The suggested agent name based on entrypoint path
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
The selected or entered agent name
|
|
36
|
+
"""
|
|
37
|
+
if self.non_interactive:
|
|
38
|
+
_print_success(f"Agent name (inferred): {suggested_name}")
|
|
39
|
+
return suggested_name
|
|
40
|
+
|
|
41
|
+
console.print(f"\n🏷️ [cyan]Inferred agent name[/cyan]: {suggested_name}")
|
|
42
|
+
console.print("[dim]Press Enter to use this name, or type a different one (alphanumeric without '-')[/dim]")
|
|
43
|
+
agent_name = _prompt_with_default("Agent name", suggested_name)
|
|
44
|
+
|
|
45
|
+
if not agent_name:
|
|
46
|
+
agent_name = suggested_name
|
|
47
|
+
|
|
48
|
+
_print_success(f"Using agent name: [cyan]{agent_name}[/cyan]")
|
|
49
|
+
return agent_name
|
|
25
50
|
|
|
26
51
|
def prompt_execution_role(self) -> Optional[str]:
|
|
27
52
|
"""Prompt for execution role. Returns role name/ARN or None for auto-creation."""
|
|
@@ -205,7 +230,7 @@ class ConfigurationManager:
|
|
|
205
230
|
Returns:
|
|
206
231
|
Tuple of (enable_memory, enable_ltm)
|
|
207
232
|
"""
|
|
208
|
-
console.print("\n
|
|
233
|
+
console.print("\n[cyan]Memory Configuration[/cyan]")
|
|
209
234
|
console.print("Short-term memory stores conversation within sessions.")
|
|
210
235
|
console.print("Long-term memory extracts preferences and facts across sessions.")
|
|
211
236
|
console.print()
|
|
@@ -238,7 +263,7 @@ class ConfigurationManager:
|
|
|
238
263
|
return enable_memory, enable_ltm
|
|
239
264
|
|
|
240
265
|
def prompt_memory_selection(self) -> Tuple[str, str]:
|
|
241
|
-
"""Prompt user to select existing memory or create new.
|
|
266
|
+
"""Prompt user to select existing memory or create new (no skip option).
|
|
242
267
|
|
|
243
268
|
Returns:
|
|
244
269
|
Tuple of (action, value) where:
|
|
@@ -249,23 +274,26 @@ class ConfigurationManager:
|
|
|
249
274
|
# In non-interactive mode, default to creating new STM
|
|
250
275
|
return ("CREATE_NEW", "STM_ONLY")
|
|
251
276
|
|
|
252
|
-
console.print("\n
|
|
277
|
+
console.print("\n[cyan]Memory Configuration[/cyan]")
|
|
278
|
+
console.print("[dim]Tip: Use --disable-memory flag to skip memory entirely[/dim]\n")
|
|
253
279
|
|
|
254
280
|
# Try to list existing memories
|
|
255
281
|
try:
|
|
256
282
|
from ...operations.memory.manager import MemoryManager
|
|
257
283
|
|
|
258
|
-
#
|
|
259
|
-
region = self.existing_config.aws.region if self.existing_config else None
|
|
284
|
+
# Get region from passed parameter OR existing config
|
|
285
|
+
region = self.region or (self.existing_config.aws.region if self.existing_config else None)
|
|
286
|
+
|
|
260
287
|
if not region:
|
|
261
|
-
#
|
|
288
|
+
# No region available - skip to new memory creation
|
|
289
|
+
console.print("[dim]No region configured yet, proceeding with new memory creation[/dim]")
|
|
262
290
|
return self._prompt_new_memory_config()
|
|
263
291
|
|
|
264
292
|
memory_manager = MemoryManager(region_name=region)
|
|
265
293
|
existing_memories = memory_manager.list_memories(max_results=10)
|
|
266
294
|
|
|
267
295
|
if existing_memories:
|
|
268
|
-
console.print("
|
|
296
|
+
console.print("[cyan]Existing memory resources found:[/cyan]")
|
|
269
297
|
for i, mem in enumerate(existing_memories, 1):
|
|
270
298
|
# Display memory summary
|
|
271
299
|
mem_id = mem.get("id", "unknown")
|
|
@@ -283,7 +311,6 @@ class ConfigurationManager:
|
|
|
283
311
|
console.print("\n[dim]Options:[/dim]")
|
|
284
312
|
console.print("[dim] • Enter a number to use existing memory[/dim]")
|
|
285
313
|
console.print("[dim] • Press Enter to create new memory[/dim]")
|
|
286
|
-
console.print("[dim] • Type 's' to skip memory setup[/dim]")
|
|
287
314
|
|
|
288
315
|
response = _prompt_with_default("Your choice", "").strip().lower()
|
|
289
316
|
|
|
@@ -293,9 +320,11 @@ class ConfigurationManager:
|
|
|
293
320
|
selected = existing_memories[idx]
|
|
294
321
|
_print_success(f"Using existing memory: {selected.get('name', selected.get('id'))}")
|
|
295
322
|
return ("USE_EXISTING", selected.get("id"))
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
323
|
+
else:
|
|
324
|
+
# No existing memories found
|
|
325
|
+
console.print("[yellow]No existing memory resources found in your account[/yellow]")
|
|
326
|
+
console.print("[dim]Proceeding with new memory creation...[/dim]\n")
|
|
327
|
+
|
|
299
328
|
except Exception as e:
|
|
300
329
|
console.print(f"[dim]Could not list existing memories: {e}[/dim]")
|
|
301
330
|
|
|
@@ -303,9 +332,8 @@ class ConfigurationManager:
|
|
|
303
332
|
return self._prompt_new_memory_config()
|
|
304
333
|
|
|
305
334
|
def _prompt_new_memory_config(self) -> Tuple[str, str]:
|
|
306
|
-
"""Prompt for new memory configuration."""
|
|
307
|
-
console.print("
|
|
308
|
-
console.print("[green]✓ Short-term memory is enabled by default[/green]")
|
|
335
|
+
"""Prompt for new memory configuration (no skip option)."""
|
|
336
|
+
console.print("[green]✓ Short-term memory will be enabled (default)[/green]")
|
|
309
337
|
console.print(" • Stores conversations within sessions")
|
|
310
338
|
console.print(" • Provides immediate context recall")
|
|
311
339
|
console.print()
|
|
@@ -313,10 +341,10 @@ class ConfigurationManager:
|
|
|
313
341
|
console.print(" • Extracts user preferences across sessions")
|
|
314
342
|
console.print(" • Remembers facts and patterns")
|
|
315
343
|
console.print(" • Creates session summaries")
|
|
316
|
-
console.print(" • [dim]Note: Takes
|
|
344
|
+
console.print(" • [dim]Note: Takes 120-180 seconds to process[/dim]")
|
|
317
345
|
console.print()
|
|
318
346
|
|
|
319
|
-
response = _prompt_with_default("Enable long-term memory
|
|
347
|
+
response = _prompt_with_default("Enable long-term memory? (yes/no)", "no").strip().lower()
|
|
320
348
|
|
|
321
349
|
if response in ["yes", "y"]:
|
|
322
350
|
_print_success("Configuring short-term + long-term memory")
|
|
@@ -44,9 +44,11 @@ class Runtime:
|
|
|
44
44
|
auto_create_ecr: bool = True,
|
|
45
45
|
auto_create_execution_role: bool = False,
|
|
46
46
|
authorizer_configuration: Optional[Dict[str, Any]] = None,
|
|
47
|
+
request_header_configuration: Optional[Dict[str, Any]] = None,
|
|
47
48
|
region: Optional[str] = None,
|
|
48
49
|
protocol: Optional[Literal["HTTP", "MCP", "A2A"]] = None,
|
|
49
50
|
disable_otel: bool = False,
|
|
51
|
+
memory_mode: Literal["NO_MEMORY", "STM_ONLY", "STM_AND_LTM"] = "STM_ONLY",
|
|
50
52
|
non_interactive: bool = True,
|
|
51
53
|
) -> ConfigureResult:
|
|
52
54
|
"""Configure Bedrock AgentCore from notebook using an entrypoint file.
|
|
@@ -64,13 +66,31 @@ class Runtime:
|
|
|
64
66
|
auto_create_ecr: Whether to auto-create ECR repository
|
|
65
67
|
auto_create_execution_role: Whether to auto-create execution role (makes execution_role optional)
|
|
66
68
|
authorizer_configuration: JWT authorizer configuration dictionary
|
|
69
|
+
request_header_configuration: Request header configuration dictionary
|
|
67
70
|
region: AWS region for deployment
|
|
68
71
|
protocol: agent server protocol, must be either HTTP or MCP or A2A
|
|
69
72
|
disable_otel: Whether to disable OpenTelemetry observability (default: False)
|
|
73
|
+
memory_mode: Memory configuration mode (default: "STM_ONLY")
|
|
74
|
+
- "NO_MEMORY": Disable memory entirely (stateless agent)
|
|
75
|
+
- "STM_ONLY": Short-term memory only (default)
|
|
76
|
+
- "STM_AND_LTM": Short-term + long-term memory with strategy extraction
|
|
70
77
|
non_interactive: Skip interactive prompts and use defaults (default: True)
|
|
71
78
|
|
|
72
79
|
Returns:
|
|
73
80
|
ConfigureResult with configuration details
|
|
81
|
+
|
|
82
|
+
Example:
|
|
83
|
+
# Default: STM only (backward compatible)
|
|
84
|
+
runtime.configure(entrypoint='handler.py')
|
|
85
|
+
|
|
86
|
+
# Explicitly enable LTM
|
|
87
|
+
runtime.configure(entrypoint='handler.py', memory_mode='STM_AND_LTM')
|
|
88
|
+
|
|
89
|
+
# Disable memory entirely
|
|
90
|
+
runtime.configure(entrypoint='handler.py', memory_mode='NO_MEMORY')
|
|
91
|
+
|
|
92
|
+
# Invalid - raises error
|
|
93
|
+
runtime.configure(entrypoint='handler.py', disable_memory=True, memory_mode='STM_AND_LTM')
|
|
74
94
|
"""
|
|
75
95
|
if protocol and protocol.upper() not in ["HTTP", "MCP", "A2A"]:
|
|
76
96
|
raise ValueError("protocol must be either HTTP or MCP or A2A")
|
|
@@ -99,7 +119,7 @@ class Runtime:
|
|
|
99
119
|
handler_dir = Path(file_path).parent
|
|
100
120
|
req_file_path = handler_dir / "requirements.txt"
|
|
101
121
|
|
|
102
|
-
all_requirements = []
|
|
122
|
+
all_requirements = []
|
|
103
123
|
all_requirements.extend(requirements)
|
|
104
124
|
|
|
105
125
|
req_file_path.write_text("\n".join(all_requirements))
|
|
@@ -107,6 +127,13 @@ class Runtime:
|
|
|
107
127
|
|
|
108
128
|
final_requirements_file = str(req_file_path)
|
|
109
129
|
|
|
130
|
+
if memory_mode == "NO_MEMORY":
|
|
131
|
+
log.info("Memory disabled - agent will be stateless")
|
|
132
|
+
elif memory_mode == "STM_AND_LTM":
|
|
133
|
+
log.info("Memory configured with STM + LTM")
|
|
134
|
+
else: # STM_ONLY
|
|
135
|
+
log.info("Memory configured with STM only")
|
|
136
|
+
|
|
110
137
|
# Configure using the operations module
|
|
111
138
|
result = configure_bedrock_agentcore(
|
|
112
139
|
agent_name=agent_name,
|
|
@@ -118,8 +145,10 @@ class Runtime:
|
|
|
118
145
|
container_runtime=container_runtime,
|
|
119
146
|
auto_create_ecr=auto_create_ecr,
|
|
120
147
|
enable_observability=not disable_otel,
|
|
148
|
+
memory_mode=memory_mode,
|
|
121
149
|
requirements_file=final_requirements_file,
|
|
122
150
|
authorizer_configuration=authorizer_configuration,
|
|
151
|
+
request_header_configuration=request_header_configuration,
|
|
123
152
|
region=region,
|
|
124
153
|
protocol=protocol.upper() if protocol else None,
|
|
125
154
|
non_interactive=non_interactive,
|
|
@@ -451,7 +451,7 @@ class MemoryManager:
|
|
|
451
451
|
logger.info("🔎 Retrieving memory resource with ID: %s...", memory_id)
|
|
452
452
|
try:
|
|
453
453
|
response = self._control_plane_client.get_memory(memoryId=memory_id).get("memory", {})
|
|
454
|
-
logger.info("
|
|
454
|
+
logger.info(" Found memory: %s", memory_id)
|
|
455
455
|
return Memory(response)
|
|
456
456
|
except ClientError as e:
|
|
457
457
|
logger.error(" ❌ Error retrieving memory: %s", e)
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
"""Bedrock AgentCore operations - shared business logic for CLI and notebook interfaces."""
|
|
2
2
|
|
|
3
|
-
from .configure import
|
|
3
|
+
from .configure import (
|
|
4
|
+
configure_bedrock_agentcore,
|
|
5
|
+
detect_entrypoint,
|
|
6
|
+
detect_requirements,
|
|
7
|
+
get_relative_path,
|
|
8
|
+
infer_agent_name,
|
|
9
|
+
validate_agent_name,
|
|
10
|
+
)
|
|
4
11
|
from .destroy import destroy_bedrock_agentcore
|
|
5
12
|
from .invoke import invoke_bedrock_agentcore
|
|
6
13
|
from .launch import launch_bedrock_agentcore
|
|
@@ -18,6 +25,10 @@ __all__ = [
|
|
|
18
25
|
"configure_bedrock_agentcore",
|
|
19
26
|
"destroy_bedrock_agentcore",
|
|
20
27
|
"validate_agent_name",
|
|
28
|
+
"detect_entrypoint",
|
|
29
|
+
"detect_requirements",
|
|
30
|
+
"get_relative_path",
|
|
31
|
+
"infer_agent_name",
|
|
21
32
|
"launch_bedrock_agentcore",
|
|
22
33
|
"invoke_bedrock_agentcore",
|
|
23
34
|
"get_status",
|