bedrock-agentcore-starter-toolkit 0.1.22__py3-none-any.whl → 0.1.24__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/utils/runtime/templates/execution_role_policy.json.j2 +0 -13
- {bedrock_agentcore_starter_toolkit-0.1.22.dist-info → bedrock_agentcore_starter_toolkit-0.1.24.dist-info}/METADATA +1 -1
- {bedrock_agentcore_starter_toolkit-0.1.22.dist-info → bedrock_agentcore_starter_toolkit-0.1.24.dist-info}/RECORD +23 -23
- {bedrock_agentcore_starter_toolkit-0.1.22.dist-info → bedrock_agentcore_starter_toolkit-0.1.24.dist-info}/WHEEL +0 -0
- {bedrock_agentcore_starter_toolkit-0.1.22.dist-info → bedrock_agentcore_starter_toolkit-0.1.24.dist-info}/entry_points.txt +0 -0
- {bedrock_agentcore_starter_toolkit-0.1.22.dist-info → bedrock_agentcore_starter_toolkit-0.1.24.dist-info}/licenses/LICENSE.txt +0 -0
- {bedrock_agentcore_starter_toolkit-0.1.22.dist-info → bedrock_agentcore_starter_toolkit-0.1.24.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,22 +50,50 @@ 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
|
|
@@ -76,49 +107,45 @@ def _handle_requirements_file_display(
|
|
|
76
107
|
Args:
|
|
77
108
|
requirements_file: Explicit requirements file path
|
|
78
109
|
non_interactive: Whether to skip interactive prompts
|
|
79
|
-
source_path: Optional source code directory
|
|
110
|
+
source_path: Optional source code directory
|
|
80
111
|
"""
|
|
81
|
-
from ...utils.runtime.entrypoint import detect_dependencies
|
|
82
|
-
|
|
83
112
|
if requirements_file:
|
|
84
113
|
# User provided file - validate and show confirmation
|
|
85
114
|
return _validate_requirements_file(requirements_file)
|
|
86
115
|
|
|
87
|
-
#
|
|
88
|
-
|
|
89
|
-
# - Otherwise: check project root (Path.cwd())
|
|
90
|
-
if source_path:
|
|
91
|
-
source_dir = Path(source_path)
|
|
92
|
-
deps = detect_dependencies(source_dir)
|
|
93
|
-
else:
|
|
94
|
-
# No source_path, check project root
|
|
95
|
-
deps = detect_dependencies(Path.cwd())
|
|
116
|
+
# Use operations layer for detection - source_path is always provided
|
|
117
|
+
deps = detect_requirements(Path(source_path))
|
|
96
118
|
|
|
97
119
|
if non_interactive:
|
|
98
120
|
# Auto-detection for non-interactive mode
|
|
99
121
|
if deps.found:
|
|
100
|
-
|
|
122
|
+
rel_deps_path = get_relative_path(Path(deps.resolved_path))
|
|
123
|
+
_print_success(f"Using detected requirements file: [cyan]{rel_deps_path}[/cyan]")
|
|
101
124
|
return None # Use detected file
|
|
102
125
|
else:
|
|
103
126
|
_handle_error("No requirements file specified and none found automatically")
|
|
104
127
|
|
|
105
128
|
# Auto-detection with interactive prompt
|
|
106
129
|
if deps.found:
|
|
107
|
-
|
|
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]")
|
|
108
133
|
console.print("[dim]Press Enter to use this file, or type a different path (use Tab for autocomplete):[/dim]")
|
|
109
134
|
|
|
110
|
-
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
|
+
)
|
|
111
138
|
|
|
112
139
|
if result is None:
|
|
113
140
|
# Use detected file
|
|
114
|
-
_print_success(f"Using detected file: [
|
|
141
|
+
_print_success(f"Using detected requirements file: [cyan]{rel_deps_path}[/cyan]")
|
|
115
142
|
|
|
116
143
|
return result
|
|
117
144
|
else:
|
|
118
145
|
console.print("\n[yellow]⚠️ No dependency file found (requirements.txt or pyproject.toml)[/yellow]")
|
|
119
146
|
console.print("[dim]Enter path to requirements file (use Tab for autocomplete), or press Enter to skip:[/dim]")
|
|
120
147
|
|
|
121
|
-
result = _prompt_for_requirements_file("Path: ")
|
|
148
|
+
result = _prompt_for_requirements_file("Path: ", source_path=source_path)
|
|
122
149
|
|
|
123
150
|
if result is None:
|
|
124
151
|
_handle_error("No requirements file specified and none found automatically")
|
|
@@ -126,6 +153,29 @@ def _handle_requirements_file_display(
|
|
|
126
153
|
return result
|
|
127
154
|
|
|
128
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
|
+
|
|
129
179
|
# Define options at module level to avoid B008
|
|
130
180
|
ENV_OPTION = typer.Option(None, "--env", "-env", help="Environment variables for local mode (format: KEY=VALUE)")
|
|
131
181
|
|
|
@@ -179,7 +229,12 @@ def set_default(name: str = typer.Argument(...)):
|
|
|
179
229
|
@configure_app.callback(invoke_without_command=True)
|
|
180
230
|
def configure(
|
|
181
231
|
ctx: typer.Context,
|
|
182
|
-
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
|
+
),
|
|
183
238
|
agent_name: Optional[str] = typer.Option(None, "--name", "-n"),
|
|
184
239
|
execution_role: Optional[str] = typer.Option(None, "--execution-role", "-er"),
|
|
185
240
|
code_build_execution_role: Optional[str] = typer.Option(None, "--code-build-execution-role", "-cber"),
|
|
@@ -189,6 +244,7 @@ def configure(
|
|
|
189
244
|
None, "--requirements-file", "-rf", help="Path to requirements file"
|
|
190
245
|
),
|
|
191
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"),
|
|
192
248
|
authorizer_config: Optional[str] = typer.Option(
|
|
193
249
|
None, "--authorizer-config", "-ac", help="OAuth authorizer configuration as JSON string"
|
|
194
250
|
),
|
|
@@ -205,35 +261,75 @@ def configure(
|
|
|
205
261
|
non_interactive: bool = typer.Option(
|
|
206
262
|
False, "--non-interactive", "-ni", help="Skip prompts; use defaults unless overridden"
|
|
207
263
|
),
|
|
208
|
-
source_path: Optional[str] = typer.Option(None, "--source-path", "-sp", help="Path to agent source code directory"),
|
|
209
264
|
):
|
|
210
|
-
"""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
|
+
"""
|
|
211
272
|
if ctx.invoked_subcommand is not None:
|
|
212
273
|
return
|
|
213
274
|
|
|
214
|
-
if not entrypoint:
|
|
215
|
-
_handle_error("--entrypoint is required")
|
|
216
|
-
|
|
217
275
|
if protocol and protocol.upper() not in ["HTTP", "MCP", "A2A"]:
|
|
218
276
|
_handle_error("Error: --protocol must be either HTTP or MCP or A2A")
|
|
219
277
|
|
|
220
278
|
console.print("[cyan]Configuring Bedrock AgentCore...[/cyan]")
|
|
221
|
-
try:
|
|
222
|
-
_, file_name = parse_entrypoint(entrypoint)
|
|
223
|
-
agent_name = agent_name or file_name
|
|
224
|
-
|
|
225
|
-
valid, error = validate_agent_name(agent_name)
|
|
226
|
-
if not valid:
|
|
227
|
-
_handle_error(error)
|
|
228
|
-
|
|
229
|
-
console.print(f"[dim]Agent name: {agent_name}[/dim]")
|
|
230
|
-
except ValueError as e:
|
|
231
|
-
_handle_error(f"Error: {e}", e)
|
|
232
279
|
|
|
233
|
-
# Create configuration manager for
|
|
280
|
+
# Create configuration manager early for consistent prompting
|
|
234
281
|
config_path = Path.cwd() / ".bedrock_agentcore.yaml"
|
|
235
282
|
config_manager = ConfigurationManager(config_path, non_interactive)
|
|
236
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
|
+
|
|
237
333
|
# Interactive prompts for missing values - clean and elegant
|
|
238
334
|
if not execution_role:
|
|
239
335
|
execution_role = config_manager.prompt_execution_role()
|
|
@@ -252,9 +348,6 @@ def configure(
|
|
|
252
348
|
auto_create_ecr = False
|
|
253
349
|
_print_success(f"Using existing ECR repository: [dim]{ecr_repository}[/dim]")
|
|
254
350
|
|
|
255
|
-
# Handle dependency file selection with simplified logic
|
|
256
|
-
final_requirements_file = _handle_requirements_file_display(requirements_file, non_interactive, source_path)
|
|
257
|
-
|
|
258
351
|
# Handle OAuth authorization configuration
|
|
259
352
|
oauth_config = None
|
|
260
353
|
if authorizer_config:
|
|
@@ -280,6 +373,11 @@ def configure(
|
|
|
280
373
|
else:
|
|
281
374
|
request_header_config = config_manager.prompt_request_header_allowlist()
|
|
282
375
|
|
|
376
|
+
if disable_memory:
|
|
377
|
+
memory_mode_value = "NO_MEMORY"
|
|
378
|
+
else:
|
|
379
|
+
memory_mode_value = "STM_ONLY"
|
|
380
|
+
|
|
283
381
|
try:
|
|
284
382
|
result = configure_bedrock_agentcore(
|
|
285
383
|
agent_name=agent_name,
|
|
@@ -290,6 +388,7 @@ def configure(
|
|
|
290
388
|
container_runtime=container_runtime,
|
|
291
389
|
auto_create_ecr=auto_create_ecr,
|
|
292
390
|
enable_observability=not disable_otel,
|
|
391
|
+
memory_mode=memory_mode_value,
|
|
293
392
|
requirements_file=final_requirements_file,
|
|
294
393
|
authorizer_configuration=oauth_config,
|
|
295
394
|
request_header_configuration=request_header_config,
|
|
@@ -311,22 +410,26 @@ def configure(
|
|
|
311
410
|
headers = request_header_config.get("requestHeaderAllowlist", [])
|
|
312
411
|
headers_info = f"Request Headers Allowlist: [dim]{len(headers)} headers configured[/dim]\n"
|
|
313
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
|
+
|
|
314
418
|
console.print(
|
|
315
419
|
Panel(
|
|
316
|
-
f"[
|
|
317
|
-
f"[bold]Agent Details:[/bold]\n"
|
|
420
|
+
f"[bold]Agent Details[/bold]\n"
|
|
318
421
|
f"Agent Name: [cyan]{agent_name}[/cyan]\n"
|
|
319
422
|
f"Runtime: [cyan]{result.runtime}[/cyan]\n"
|
|
320
423
|
f"Region: [cyan]{result.region}[/cyan]\n"
|
|
321
|
-
f"Account: [
|
|
322
|
-
f"[bold]Configuration
|
|
323
|
-
f"Execution Role: [
|
|
324
|
-
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]"
|
|
325
428
|
f"{'Auto-create' if result.auto_create_ecr else result.ecr_repository or 'N/A'}"
|
|
326
|
-
f"[/
|
|
327
|
-
f"Authorization: [
|
|
429
|
+
f"[/cyan]\n"
|
|
430
|
+
f"Authorization: [cyan]{auth_info}[/cyan]\n\n"
|
|
328
431
|
f"{headers_info}\n"
|
|
329
|
-
f"Memory: [
|
|
432
|
+
f"Memory: [cyan]{memory_info}[/cyan]\n\n"
|
|
330
433
|
f"📄 Config saved to: [dim]{result.config_path}[/dim]\n\n"
|
|
331
434
|
f"[bold]Next Steps:[/bold]\n"
|
|
332
435
|
f" [cyan]agentcore launch[/cyan]",
|
|
@@ -490,7 +593,6 @@ def launch(
|
|
|
490
593
|
region = agent_config.aws.region if agent_config else "us-east-1"
|
|
491
594
|
|
|
492
595
|
deploy_panel = (
|
|
493
|
-
f"✅ [green]CodeBuild Deployment Successful![/green]\n\n"
|
|
494
596
|
f"[bold]Agent Details:[/bold]\n"
|
|
495
597
|
f"Agent Name: [cyan]{agent_name}[/cyan]\n"
|
|
496
598
|
f"Agent ARN: [cyan]{result.agent_arn}[/cyan]\n"
|
|
@@ -530,15 +632,12 @@ def launch(
|
|
|
530
632
|
|
|
531
633
|
if local_build:
|
|
532
634
|
title = "Local Build Success"
|
|
533
|
-
deployment_type = "✅ [green]Local Build Deployment Successful![/green]"
|
|
534
635
|
icon = "🔧"
|
|
535
636
|
else:
|
|
536
637
|
title = "Deployment Success"
|
|
537
|
-
deployment_type = "✅ [green]Deployment Successful![/green]"
|
|
538
638
|
icon = "🚀"
|
|
539
639
|
|
|
540
640
|
deploy_panel = (
|
|
541
|
-
f"{deployment_type}\n\n"
|
|
542
641
|
f"[bold]Agent Details:[/bold]\n"
|
|
543
642
|
f"Agent Name: [cyan]{agent_name}[/cyan]\n"
|
|
544
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,8 +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("[green]✓ Short-term memory
|
|
335
|
+
"""Prompt for new memory configuration (no skip option)."""
|
|
336
|
+
console.print("[green]✓ Short-term memory will be enabled (default)[/green]")
|
|
308
337
|
console.print(" • Stores conversations within sessions")
|
|
309
338
|
console.print(" • Provides immediate context recall")
|
|
310
339
|
console.print()
|
|
@@ -312,10 +341,10 @@ class ConfigurationManager:
|
|
|
312
341
|
console.print(" • Extracts user preferences across sessions")
|
|
313
342
|
console.print(" • Remembers facts and patterns")
|
|
314
343
|
console.print(" • Creates session summaries")
|
|
315
|
-
console.print(" • [dim]Note: Takes
|
|
344
|
+
console.print(" • [dim]Note: Takes 120-180 seconds to process[/dim]")
|
|
316
345
|
console.print()
|
|
317
346
|
|
|
318
|
-
response = _prompt_with_default("Enable long-term memory
|
|
347
|
+
response = _prompt_with_default("Enable long-term memory? (yes/no)", "no").strip().lower()
|
|
319
348
|
|
|
320
349
|
if response in ["yes", "y"]:
|
|
321
350
|
_print_success("Configuring short-term + long-term memory")
|
|
@@ -48,6 +48,7 @@ class Runtime:
|
|
|
48
48
|
region: Optional[str] = None,
|
|
49
49
|
protocol: Optional[Literal["HTTP", "MCP", "A2A"]] = None,
|
|
50
50
|
disable_otel: bool = False,
|
|
51
|
+
memory_mode: Literal["NO_MEMORY", "STM_ONLY", "STM_AND_LTM"] = "STM_ONLY",
|
|
51
52
|
non_interactive: bool = True,
|
|
52
53
|
) -> ConfigureResult:
|
|
53
54
|
"""Configure Bedrock AgentCore from notebook using an entrypoint file.
|
|
@@ -69,10 +70,27 @@ class Runtime:
|
|
|
69
70
|
region: AWS region for deployment
|
|
70
71
|
protocol: agent server protocol, must be either HTTP or MCP or A2A
|
|
71
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
|
|
72
77
|
non_interactive: Skip interactive prompts and use defaults (default: True)
|
|
73
78
|
|
|
74
79
|
Returns:
|
|
75
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')
|
|
76
94
|
"""
|
|
77
95
|
if protocol and protocol.upper() not in ["HTTP", "MCP", "A2A"]:
|
|
78
96
|
raise ValueError("protocol must be either HTTP or MCP or A2A")
|
|
@@ -101,7 +119,7 @@ class Runtime:
|
|
|
101
119
|
handler_dir = Path(file_path).parent
|
|
102
120
|
req_file_path = handler_dir / "requirements.txt"
|
|
103
121
|
|
|
104
|
-
all_requirements = []
|
|
122
|
+
all_requirements = []
|
|
105
123
|
all_requirements.extend(requirements)
|
|
106
124
|
|
|
107
125
|
req_file_path.write_text("\n".join(all_requirements))
|
|
@@ -109,6 +127,13 @@ class Runtime:
|
|
|
109
127
|
|
|
110
128
|
final_requirements_file = str(req_file_path)
|
|
111
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
|
+
|
|
112
137
|
# Configure using the operations module
|
|
113
138
|
result = configure_bedrock_agentcore(
|
|
114
139
|
agent_name=agent_name,
|
|
@@ -120,6 +145,7 @@ class Runtime:
|
|
|
120
145
|
container_runtime=container_runtime,
|
|
121
146
|
auto_create_ecr=auto_create_ecr,
|
|
122
147
|
enable_observability=not disable_otel,
|
|
148
|
+
memory_mode=memory_mode,
|
|
123
149
|
requirements_file=final_requirements_file,
|
|
124
150
|
authorizer_configuration=authorizer_configuration,
|
|
125
151
|
request_header_configuration=request_header_configuration,
|
|
@@ -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",
|