bedrock-agentcore-starter-toolkit 0.1.26__py3-none-any.whl → 0.1.27__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of bedrock-agentcore-starter-toolkit might be problematic. Click here for more details.

Files changed (23) hide show
  1. bedrock_agentcore_starter_toolkit/cli/cli.py +9 -1
  2. bedrock_agentcore_starter_toolkit/cli/runtime/commands.py +250 -7
  3. bedrock_agentcore_starter_toolkit/cli/runtime/configuration_manager.py +31 -7
  4. bedrock_agentcore_starter_toolkit/notebook/runtime/bedrock_agentcore.py +240 -2
  5. bedrock_agentcore_starter_toolkit/operations/memory/manager.py +20 -33
  6. bedrock_agentcore_starter_toolkit/operations/memory/models/strategies/base.py +2 -0
  7. bedrock_agentcore_starter_toolkit/operations/memory/models/strategies/self_managed.py +107 -0
  8. bedrock_agentcore_starter_toolkit/operations/runtime/__init__.py +4 -0
  9. bedrock_agentcore_starter_toolkit/operations/runtime/configure.py +120 -5
  10. bedrock_agentcore_starter_toolkit/operations/runtime/invoke.py +0 -53
  11. bedrock_agentcore_starter_toolkit/operations/runtime/launch.py +213 -16
  12. bedrock_agentcore_starter_toolkit/operations/runtime/models.py +19 -0
  13. bedrock_agentcore_starter_toolkit/operations/runtime/status.py +30 -0
  14. bedrock_agentcore_starter_toolkit/operations/runtime/stop_session.py +123 -0
  15. bedrock_agentcore_starter_toolkit/operations/runtime/vpc_validation.py +196 -0
  16. bedrock_agentcore_starter_toolkit/services/runtime.py +43 -1
  17. bedrock_agentcore_starter_toolkit/utils/runtime/schema.py +44 -1
  18. {bedrock_agentcore_starter_toolkit-0.1.26.dist-info → bedrock_agentcore_starter_toolkit-0.1.27.dist-info}/METADATA +8 -8
  19. {bedrock_agentcore_starter_toolkit-0.1.26.dist-info → bedrock_agentcore_starter_toolkit-0.1.27.dist-info}/RECORD +23 -20
  20. {bedrock_agentcore_starter_toolkit-0.1.26.dist-info → bedrock_agentcore_starter_toolkit-0.1.27.dist-info}/WHEEL +0 -0
  21. {bedrock_agentcore_starter_toolkit-0.1.26.dist-info → bedrock_agentcore_starter_toolkit-0.1.27.dist-info}/entry_points.txt +0 -0
  22. {bedrock_agentcore_starter_toolkit-0.1.26.dist-info → bedrock_agentcore_starter_toolkit-0.1.27.dist-info}/licenses/LICENSE.txt +0 -0
  23. {bedrock_agentcore_starter_toolkit-0.1.26.dist-info → bedrock_agentcore_starter_toolkit-0.1.27.dist-info}/licenses/NOTICE.txt +0 -0
@@ -5,7 +5,14 @@ import typer
5
5
  from ..cli.gateway.commands import create_mcp_gateway, create_mcp_gateway_target, gateway_app
6
6
  from ..utils.logging_config import setup_toolkit_logging
7
7
  from .import_agent.commands import import_agent
8
- from .runtime.commands import configure_app, destroy, invoke, launch, status
8
+ from .runtime.commands import (
9
+ configure_app,
10
+ destroy,
11
+ invoke,
12
+ launch,
13
+ status,
14
+ stop_session,
15
+ )
9
16
 
10
17
  app = typer.Typer(name="agentcore", help="BedrockAgentCore CLI", add_completion=False, rich_markup_mode="rich")
11
18
 
@@ -18,6 +25,7 @@ app.command("status")(status)
18
25
  app.command("launch")(launch)
19
26
  app.command("import-agent")(import_agent)
20
27
  app.command("destroy")(destroy)
28
+ app.command("stop-session")(stop_session)
21
29
  app.add_typer(configure_app)
22
30
 
23
31
  # gateway
@@ -257,6 +257,33 @@ def configure(
257
257
  help="Comma-separated list of allowed request headers "
258
258
  "(Authorization or X-Amzn-Bedrock-AgentCore-Runtime-Custom-*)",
259
259
  ),
260
+ vpc: bool = typer.Option(
261
+ False, "--vpc", help="Enable VPC networking mode (requires --subnets and --security-groups)"
262
+ ),
263
+ subnets: Optional[str] = typer.Option(
264
+ None,
265
+ "--subnets",
266
+ help="Comma-separated list of subnet IDs (e.g., subnet-abc123,subnet-def456). Required with --vpc.",
267
+ ),
268
+ security_groups: Optional[str] = typer.Option(
269
+ None,
270
+ "--security-groups",
271
+ help="Comma-separated list of security group IDs (e.g., sg-xyz789). Required with --vpc.",
272
+ ),
273
+ idle_timeout: Optional[int] = typer.Option(
274
+ None,
275
+ "--idle-timeout",
276
+ help="Idle runtime session timeout in seconds (60-28800, default: 900)",
277
+ min=60,
278
+ max=28800,
279
+ ),
280
+ max_lifetime: Optional[int] = typer.Option(
281
+ None,
282
+ "--max-lifetime",
283
+ help="Maximum instance lifetime in seconds (60-28800, default: 28800)",
284
+ min=60,
285
+ max=28800,
286
+ ),
260
287
  verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose output"),
261
288
  region: Optional[str] = typer.Option(None, "--region", "-r"),
262
289
  protocol: Optional[str] = typer.Option(None, "--protocol", "-p", help="Server protocol (HTTP or MCP or A2A)"),
@@ -277,6 +304,64 @@ def configure(
277
304
  if protocol and protocol.upper() not in ["HTTP", "MCP", "A2A"]:
278
305
  _handle_error("Error: --protocol must be either HTTP or MCP or A2A")
279
306
 
307
+ # Validate VPC configuration
308
+ vpc_subnets = None
309
+ vpc_security_groups = None
310
+
311
+ if vpc:
312
+ # VPC mode requires both subnets and security groups
313
+ if not subnets or not security_groups:
314
+ _handle_error(
315
+ "VPC mode requires both --subnets and --security-groups.\n"
316
+ "Example: agentcore configure --entrypoint my_agent.py --vpc "
317
+ "--subnets subnet-abc123,subnet-def456 --security-groups sg-xyz789"
318
+ )
319
+
320
+ # Parse and validate subnet IDs - UPDATED VALIDATION
321
+ vpc_subnets = [s.strip() for s in subnets.split(",") if s.strip()]
322
+ for subnet_id in vpc_subnets:
323
+ # Format: subnet-{8-17 hex characters}
324
+ if not subnet_id.startswith("subnet-"):
325
+ _handle_error(
326
+ f"Invalid subnet ID format: {subnet_id}\nSubnet IDs must start with 'subnet-' (e.g., subnet-abc123)"
327
+ )
328
+ # Check minimum length (subnet- + at least 8 chars)
329
+ if len(subnet_id) < 15: # "subnet-" (7) + 8 chars = 15
330
+ _handle_error(
331
+ f"Invalid subnet ID format: {subnet_id}\nSubnet ID is too short. Expected format: subnet-xxxxxxxx"
332
+ )
333
+
334
+ # Parse and validate security group IDs - UPDATED VALIDATION
335
+ vpc_security_groups = [sg.strip() for sg in security_groups.split(",") if sg.strip()]
336
+ for sg_id in vpc_security_groups:
337
+ # Format: sg-{8-17 hex characters}
338
+ if not sg_id.startswith("sg-"):
339
+ _handle_error(
340
+ f"Invalid security group ID format: {sg_id}\n"
341
+ f"Security group IDs must start with 'sg-' (e.g., sg-abc123)"
342
+ )
343
+ # Check minimum length (sg- + at least 8 chars)
344
+ if len(sg_id) < 11: # "sg-" (3) + 8 chars = 11
345
+ _handle_error(
346
+ f"Invalid security group ID format: {sg_id}\n"
347
+ f"Security group ID is too short. Expected format: sg-xxxxxxxx"
348
+ )
349
+
350
+ _print_success(
351
+ f"VPC mode enabled with {len(vpc_subnets)} subnets and {len(vpc_security_groups)} security groups"
352
+ )
353
+
354
+ elif subnets or security_groups:
355
+ # Error: VPC resources provided without --vpc flag
356
+ _handle_error(
357
+ "The --subnets and --security-groups flags require --vpc flag.\n"
358
+ "Use: agentcore configure --entrypoint my_agent.py --vpc --subnets ... --security-groups ..."
359
+ )
360
+ # Validate lifecycle configuration
361
+ if idle_timeout is not None and max_lifetime is not None:
362
+ if idle_timeout > max_lifetime:
363
+ _handle_error(f"Error: --idle-timeout ({idle_timeout}s) must be <= --max-lifetime ({max_lifetime}s)")
364
+
280
365
  console.print("[cyan]Configuring Bedrock AgentCore...[/cyan]")
281
366
 
282
367
  # Create configuration manager early for consistent prompting
@@ -399,6 +484,11 @@ def configure(
399
484
  protocol=protocol.upper() if protocol else None,
400
485
  non_interactive=non_interactive,
401
486
  source_path=source_path,
487
+ vpc_enabled=vpc,
488
+ vpc_subnets=vpc_subnets,
489
+ vpc_security_groups=vpc_security_groups,
490
+ idle_timeout=idle_timeout,
491
+ max_lifetime=max_lifetime,
402
492
  )
403
493
 
404
494
  # Prepare authorization info for summary
@@ -412,10 +502,29 @@ def configure(
412
502
  headers = request_header_config.get("requestHeaderAllowlist", [])
413
503
  headers_info = f"Request Headers Allowlist: [dim]{len(headers)} headers configured[/dim]\n"
414
504
 
505
+ network_info = "Public"
506
+ if vpc:
507
+ network_info = f"VPC ({len(vpc_subnets)} subnets, {len(vpc_security_groups)} security groups)"
508
+
415
509
  execution_role_display = "Auto-create" if not result.execution_role else result.execution_role
416
- memory_info = "Short-term memory (30-day retention)"
417
- if disable_memory:
510
+ saved_config = load_config(result.config_path)
511
+ saved_agent = saved_config.get_agent_config(agent_name)
512
+
513
+ # Display memory status based on actual configuration
514
+ if saved_agent.memory.mode == "NO_MEMORY":
418
515
  memory_info = "Disabled"
516
+ elif saved_agent.memory.mode == "STM_AND_LTM":
517
+ memory_info = "Short-term + Long-term memory (30-day retention)"
518
+ else: # STM_ONLY
519
+ memory_info = "Short-term memory (30-day retention)"
520
+
521
+ lifecycle_info = ""
522
+ if idle_timeout or max_lifetime:
523
+ lifecycle_info = "\n[bold]Lifecycle Settings:[/bold]\n"
524
+ if idle_timeout:
525
+ lifecycle_info += f"Idle Timeout: [cyan]{idle_timeout}s ({idle_timeout // 60} minutes)[/cyan]\n"
526
+ if max_lifetime:
527
+ lifecycle_info += f"Max Lifetime: [cyan]{max_lifetime}s ({max_lifetime // 3600} hours)[/cyan]\n"
419
528
 
420
529
  console.print(
421
530
  Panel(
@@ -429,9 +538,11 @@ def configure(
429
538
  f"ECR Repository: [cyan]"
430
539
  f"{'Auto-create' if result.auto_create_ecr else result.ecr_repository or 'N/A'}"
431
540
  f"[/cyan]\n"
541
+ f"Network Mode: [cyan]{network_info}[/cyan]\n"
432
542
  f"Authorization: [cyan]{auth_info}[/cyan]\n\n"
433
543
  f"{headers_info}\n"
434
544
  f"Memory: [cyan]{memory_info}[/cyan]\n\n"
545
+ f"{lifecycle_info}\n"
435
546
  f"📄 Config saved to: [dim]{result.config_path}[/dim]\n\n"
436
547
  f"[bold]Next Steps:[/bold]\n"
437
548
  f" [cyan]agentcore launch[/cyan]",
@@ -568,6 +679,7 @@ def launch(
568
679
  use_codebuild=not local_build,
569
680
  env_vars=env_vars,
570
681
  auto_update_on_conflict=auto_update_on_conflict,
682
+ console=console,
571
683
  )
572
684
 
573
685
  project_config = load_config(config_path)
@@ -983,11 +1095,6 @@ def status(
983
1095
 
984
1096
  # Determine overall status
985
1097
  endpoint_status = endpoint_data.get("status", "Unknown") if endpoint_data else "Not Ready"
986
- # memory_info = ""
987
- # if hasattr(status_json["config"], "memory_id") and status_json["config"].get("memory_id"):
988
- # memory_type = status_json["config"].get("memory_type", "Short-term")
989
- # memory_id = status_json["config"].get("memory_id")
990
- # memory_info = f"Memory: [cyan]{memory_type}[/cyan] ([dim]{memory_id}[/dim])\n"
991
1098
  if endpoint_status == "READY":
992
1099
  status_text = "Ready - Agent deployed and endpoint available"
993
1100
  else:
@@ -1005,6 +1112,26 @@ def status(
1005
1112
  f"Account: [dim]{status_json['config'].get('account', 'Not available')}[/dim]\n\n"
1006
1113
  )
1007
1114
 
1115
+ # Add network information
1116
+ network_mode = status_json.get("agent", {}).get("networkConfiguration", {}).get("networkMode")
1117
+ if network_mode == "VPC":
1118
+ # Get VPC info from agent response (not config)
1119
+ network_config = (
1120
+ status_json.get("agent", {}).get("networkConfiguration", {}).get("networkModeConfig", {})
1121
+ )
1122
+ vpc_subnets = network_config.get("subnets", [])
1123
+ vpc_security_groups = network_config.get("securityGroups", [])
1124
+ subnet_count = len(vpc_subnets)
1125
+ sg_count = len(vpc_security_groups)
1126
+ vpc_id = status_json.get("config", {}).get("network_vpc_id", "unknown")
1127
+ if vpc_id:
1128
+ panel_content += f"Network: [cyan]VPC[/cyan] ([dim]{vpc_id}[/dim])\n"
1129
+ panel_content += f" {subnet_count} subnets, {sg_count} security groups\n\n"
1130
+ else:
1131
+ panel_content += "Network: [cyan]VPC[/cyan]\n\n"
1132
+ else:
1133
+ panel_content += "Network: [cyan]Public[/cyan]\n\n"
1134
+
1008
1135
  # Add memory status with proper provisioning indication
1009
1136
  if "memory_id" in status_json.get("config", {}) and status_json["config"]["memory_id"]:
1010
1137
  memory_type = status_json["config"].get("memory_type", "Unknown")
@@ -1034,6 +1161,19 @@ def status(
1034
1161
  f"[/dim]\n\n"
1035
1162
  )
1036
1163
 
1164
+ if status_json["config"].get("idle_timeout") or status_json["config"].get("max_lifetime"):
1165
+ panel_content += "[bold]Lifecycle Settings:[/bold]\n"
1166
+
1167
+ idle = status_json["config"].get("idle_timeout")
1168
+ if idle:
1169
+ panel_content += f"Idle Timeout: [cyan]{idle}s ({idle // 60} minutes)[/cyan]\n"
1170
+
1171
+ max_life = status_json["config"].get("max_lifetime")
1172
+ if max_life:
1173
+ panel_content += f"Max Lifetime: [cyan]{max_life}s ({max_life // 3600} hours)[/cyan]\n"
1174
+
1175
+ panel_content += "\n"
1176
+
1037
1177
  # Add CloudWatch logs information
1038
1178
  agent_id = status_json.get("config", {}).get("agent_id")
1039
1179
  if agent_id:
@@ -1128,6 +1268,109 @@ def status(
1128
1268
  raise typer.Exit(1) from e
1129
1269
 
1130
1270
 
1271
+ def stop_session(
1272
+ session_id: Optional[str] = typer.Option(
1273
+ None,
1274
+ "--session-id",
1275
+ "-s",
1276
+ help="Runtime session ID to stop. If not provided, stops the last active session from invoke.",
1277
+ ),
1278
+ agent: Optional[str] = typer.Option(
1279
+ None,
1280
+ "--agent",
1281
+ "-a",
1282
+ help="Agent name (use 'agentcore configure list' to see available agents)",
1283
+ ),
1284
+ ):
1285
+ """Stop an active runtime session.
1286
+
1287
+ Terminates the compute session for the running agent. This frees up resources
1288
+ and ends any ongoing agent processing for that session.
1289
+
1290
+ 🔍 How to find session IDs:
1291
+ • Last invoked session is automatically tracked (no flag needed)
1292
+ • Check 'agentcore status' to see the tracked session ID
1293
+ • Check CloudWatch logs for session IDs from previous invokes
1294
+ • Session IDs are also visible in the config file: .bedrock_agentcore.yaml
1295
+
1296
+ ⏱️ Session Lifecycle:
1297
+ • Runtime sessions are created when you invoke an agent
1298
+ • They automatically expire after the configured idle timeout
1299
+ • Stopping a session immediately frees resources without waiting for timeout
1300
+
1301
+ Examples:
1302
+ # Stop the last invoked session (most common)
1303
+ agentcore stop-session
1304
+
1305
+ # Stop a specific session by ID
1306
+ agentcore stop-session --session-id abc123xyz
1307
+
1308
+ # Stop last session for a specific agent
1309
+ agentcore stop-session --agent my-agent
1310
+
1311
+ # Get current session ID before stopping
1312
+ agentcore status # Shows tracked session ID
1313
+ agentcore stop-session
1314
+ """
1315
+ config_path = Path.cwd() / ".bedrock_agentcore.yaml"
1316
+
1317
+ try:
1318
+ from ...operations.runtime import stop_runtime_session
1319
+
1320
+ result = stop_runtime_session(
1321
+ config_path=config_path,
1322
+ session_id=session_id,
1323
+ agent_name=agent,
1324
+ )
1325
+
1326
+ # Show result panel
1327
+ status_icon = "✅" if result.status_code == 200 else "⚠️"
1328
+ status_color = "green" if result.status_code == 200 else "yellow"
1329
+
1330
+ console.print(
1331
+ Panel(
1332
+ f"[{status_color}]{status_icon} {result.message}[/{status_color}]\n\n"
1333
+ f"[bold]Session Details:[/bold]\n"
1334
+ f"Session ID: [cyan]{result.session_id}[/cyan]\n"
1335
+ f"Agent: [cyan]{result.agent_name}[/cyan]\n"
1336
+ f"Status Code: [cyan]{result.status_code}[/cyan]\n\n"
1337
+ f"[dim]💡 Runtime sessions automatically expire after idle timeout.\n"
1338
+ f" Manually stopping frees resources immediately.[/dim]",
1339
+ title="Session Stopped",
1340
+ border_style="bright_blue",
1341
+ )
1342
+ )
1343
+
1344
+ except FileNotFoundError:
1345
+ _show_configuration_not_found_panel()
1346
+ raise typer.Exit(1) from None
1347
+ except ValueError as e:
1348
+ console.print(
1349
+ Panel(
1350
+ f"[red]❌ Failed to Stop Session[/red]\n\n"
1351
+ f"Error: {str(e)}\n\n"
1352
+ f"[bold]How to find session IDs:[/bold]\n"
1353
+ f" • Check 'agentcore status' for the tracked session ID\n"
1354
+ f" • Check CloudWatch logs for session IDs\n"
1355
+ f" • Invoke the agent first to create a session\n\n"
1356
+ f"[dim]Note: Runtime sessions cannot be listed. You can only stop\n"
1357
+ f"the session from your last invoke or a specific session ID.[/dim]",
1358
+ title="Stop Session Error",
1359
+ border_style="red",
1360
+ )
1361
+ )
1362
+ raise typer.Exit(1) from e
1363
+ except Exception as e:
1364
+ console.print(
1365
+ Panel(
1366
+ f"[red]❌ Unexpected Error[/red]\n\n{str(e)}",
1367
+ title="Stop Session Error",
1368
+ border_style="red",
1369
+ )
1370
+ )
1371
+ raise typer.Exit(1) from e
1372
+
1373
+
1131
1374
  def destroy(
1132
1375
  agent: Optional[str] = typer.Option(
1133
1376
  None, "--agent", "-a", help="Agent name (use 'agentcore configure list' to see available agents)"
@@ -267,8 +267,8 @@ class ConfigurationManager:
267
267
 
268
268
  Returns:
269
269
  Tuple of (action, value) where:
270
- - action is "USE_EXISTING", "CREATE_NEW"
271
- - value is memory_id for USE_EXISTING, mode for CREATE_NEW
270
+ - action is "USE_EXISTING", "CREATE_NEW", "SKIP"
271
+ - value is memory_id for USE_EXISTING, mode for CREATE_NEW, None for SKIP
272
272
  """
273
273
  if self.non_interactive:
274
274
  # In non-interactive mode, default to creating new STM
@@ -285,8 +285,19 @@ class ConfigurationManager:
285
285
  region = self.region or (self.existing_config.aws.region if self.existing_config else None)
286
286
 
287
287
  if not region:
288
- # No region available - skip to new memory creation
289
- console.print("[dim]No region configured yet, proceeding with new memory creation[/dim]")
288
+ # No region available - offer skip option
289
+ console.print("[dim]No region configured yet[/dim]")
290
+ console.print("\n[dim]Options:[/dim]")
291
+ console.print("[dim] • Press Enter to create new memory[/dim]")
292
+ console.print("[dim] • Type 's' to skip memory setup[/dim]") # <-- ADD
293
+ console.print()
294
+
295
+ response = _prompt_with_default("Your choice", "").strip().lower()
296
+
297
+ if response == "s" or response == "skip": # <-- ADD
298
+ _print_success("Skipping memory configuration")
299
+ return ("SKIP", None)
300
+
290
301
  return self._prompt_new_memory_config()
291
302
 
292
303
  memory_manager = MemoryManager(region_name=region)
@@ -311,10 +322,14 @@ class ConfigurationManager:
311
322
  console.print("\n[dim]Options:[/dim]")
312
323
  console.print("[dim] • Enter a number to use existing memory[/dim]")
313
324
  console.print("[dim] • Press Enter to create new memory[/dim]")
325
+ console.print("[dim] • Type 's' to skip memory setup[/dim]")
314
326
 
315
327
  response = _prompt_with_default("Your choice", "").strip().lower()
316
328
 
317
- if response.isdigit():
329
+ if response == "s" or response == "skip":
330
+ _print_success("Skipping memory configuration")
331
+ return ("SKIP", None)
332
+ elif response.isdigit():
318
333
  idx = int(response) - 1
319
334
  if 0 <= idx < len(existing_memories):
320
335
  selected = existing_memories[idx]
@@ -323,7 +338,16 @@ class ConfigurationManager:
323
338
  else:
324
339
  # No existing memories found
325
340
  console.print("[yellow]No existing memory resources found in your account[/yellow]")
326
- console.print("[dim]Proceeding with new memory creation...[/dim]\n")
341
+ console.print("\n[dim]Options:[/dim]")
342
+ console.print("[dim] • Press Enter to create new memory[/dim]")
343
+ console.print("[dim] • Type 's' to skip memory setup[/dim]")
344
+ console.print()
345
+
346
+ response = _prompt_with_default("Your choice", "").strip().lower()
347
+
348
+ if response == "s" or response == "skip":
349
+ _print_success("Skipping memory configuration")
350
+ return ("SKIP", None)
327
351
 
328
352
  except Exception as e:
329
353
  console.print(f"[dim]Could not list existing memories: {e}[/dim]")
@@ -332,7 +356,7 @@ class ConfigurationManager:
332
356
  return self._prompt_new_memory_config()
333
357
 
334
358
  def _prompt_new_memory_config(self) -> Tuple[str, str]:
335
- """Prompt for new memory configuration (no skip option)."""
359
+ """Prompt for new memory configuration - LTM yes/no only."""
336
360
  console.print("[green]✓ Short-term memory will be enabled (default)[/green]")
337
361
  console.print(" • Stores conversations within sessions")
338
362
  console.print(" • Provides immediate context recall")