bedrock-agentcore-starter-toolkit 0.1.25__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 (25) hide show
  1. bedrock_agentcore_starter_toolkit/cli/cli.py +9 -1
  2. bedrock_agentcore_starter_toolkit/cli/runtime/commands.py +263 -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/identity/__init__.py +5 -0
  6. bedrock_agentcore_starter_toolkit/operations/identity/oauth2_callback_server.py +86 -0
  7. bedrock_agentcore_starter_toolkit/operations/memory/manager.py +20 -33
  8. bedrock_agentcore_starter_toolkit/operations/memory/models/strategies/base.py +2 -0
  9. bedrock_agentcore_starter_toolkit/operations/memory/models/strategies/self_managed.py +107 -0
  10. bedrock_agentcore_starter_toolkit/operations/runtime/__init__.py +4 -0
  11. bedrock_agentcore_starter_toolkit/operations/runtime/configure.py +120 -5
  12. bedrock_agentcore_starter_toolkit/operations/runtime/invoke.py +30 -54
  13. bedrock_agentcore_starter_toolkit/operations/runtime/launch.py +213 -16
  14. bedrock_agentcore_starter_toolkit/operations/runtime/models.py +19 -0
  15. bedrock_agentcore_starter_toolkit/operations/runtime/status.py +30 -0
  16. bedrock_agentcore_starter_toolkit/operations/runtime/stop_session.py +123 -0
  17. bedrock_agentcore_starter_toolkit/operations/runtime/vpc_validation.py +196 -0
  18. bedrock_agentcore_starter_toolkit/services/runtime.py +46 -2
  19. bedrock_agentcore_starter_toolkit/utils/runtime/schema.py +44 -1
  20. {bedrock_agentcore_starter_toolkit-0.1.25.dist-info → bedrock_agentcore_starter_toolkit-0.1.27.dist-info}/METADATA +13 -12
  21. {bedrock_agentcore_starter_toolkit-0.1.25.dist-info → bedrock_agentcore_starter_toolkit-0.1.27.dist-info}/RECORD +25 -20
  22. {bedrock_agentcore_starter_toolkit-0.1.25.dist-info → bedrock_agentcore_starter_toolkit-0.1.27.dist-info}/WHEEL +0 -0
  23. {bedrock_agentcore_starter_toolkit-0.1.25.dist-info → bedrock_agentcore_starter_toolkit-0.1.27.dist-info}/entry_points.txt +0 -0
  24. {bedrock_agentcore_starter_toolkit-0.1.25.dist-info → bedrock_agentcore_starter_toolkit-0.1.27.dist-info}/licenses/LICENSE.txt +0 -0
  25. {bedrock_agentcore_starter_toolkit-0.1.25.dist-info → bedrock_agentcore_starter_toolkit-0.1.27.dist-info}/licenses/NOTICE.txt +0 -0
@@ -5,10 +5,11 @@ import logging
5
5
  import time
6
6
  import urllib.parse
7
7
  from pathlib import Path
8
- from typing import Optional
8
+ from typing import List, Optional
9
9
 
10
10
  import boto3
11
11
  from botocore.exceptions import ClientError
12
+ from rich.console import Console
12
13
 
13
14
  from ...services.codebuild import CodeBuildService
14
15
  from ...services.ecr import deploy_to_ecr, get_or_create_ecr_repository
@@ -22,9 +23,122 @@ from .create_role import get_or_create_runtime_execution_role
22
23
  from .exceptions import RuntimeToolkitException
23
24
  from .models import LaunchResult
24
25
 
26
+ # console = Console()
27
+
25
28
  log = logging.getLogger(__name__)
26
29
 
27
30
 
31
+ def _validate_vpc_resources(session: boto3.Session, agent_config, region: str) -> None:
32
+ """Validate VPC resources exist and are in the same VPC.
33
+
34
+ Args:
35
+ session: Boto3 session
36
+ agent_config: Agent configuration
37
+ region: AWS region
38
+
39
+ Raises:
40
+ ValueError: If validation fails
41
+ """
42
+ network_config = agent_config.aws.network_configuration
43
+
44
+ if network_config.network_mode != "VPC":
45
+ return # Nothing to validate for PUBLIC mode
46
+
47
+ if not network_config.network_mode_config:
48
+ raise ValueError("VPC mode requires network configuration")
49
+
50
+ subnets = network_config.network_mode_config.subnets
51
+ security_groups = network_config.network_mode_config.security_groups
52
+
53
+ if not subnets or not security_groups:
54
+ raise ValueError("VPC mode requires both subnets and security groups")
55
+
56
+ ec2_client = session.client("ec2", region_name=region)
57
+
58
+ # Validate subnets exist and get their VPC IDs
59
+ try:
60
+ subnet_response = ec2_client.describe_subnets(SubnetIds=subnets)
61
+ subnet_vpcs = {subnet["VpcId"] for subnet in subnet_response["Subnets"]}
62
+
63
+ if len(subnet_vpcs) > 1:
64
+ raise ValueError(
65
+ f"All subnets must be in the same VPC. "
66
+ f"Found subnets in {len(subnet_vpcs)} different VPCs: {subnet_vpcs}"
67
+ )
68
+
69
+ vpc_id = subnet_vpcs.pop()
70
+ log.info("✓ All %d subnets are in VPC: %s", len(subnets), vpc_id)
71
+
72
+ except ClientError as e:
73
+ if e.response["Error"]["Code"] == "InvalidSubnetID.NotFound":
74
+ raise ValueError(f"One or more subnet IDs not found: {subnets}") from e
75
+ raise ValueError(f"Failed to validate subnets: {e}") from e
76
+
77
+ # Validate security groups exist and are in the same VPC
78
+ try:
79
+ sg_response = ec2_client.describe_security_groups(GroupIds=security_groups)
80
+ sg_vpcs = {sg["VpcId"] for sg in sg_response["SecurityGroups"]}
81
+
82
+ if len(sg_vpcs) > 1:
83
+ raise ValueError(
84
+ f"All security groups must be in the same VPC. Found {len(sg_vpcs)} different VPCs: {sg_vpcs}"
85
+ )
86
+
87
+ sg_vpc_id = sg_vpcs.pop()
88
+
89
+ if sg_vpc_id != vpc_id:
90
+ raise ValueError(
91
+ f"Security groups must be in the same VPC as subnets. "
92
+ f"Subnets are in VPC {vpc_id}, but security groups are in VPC {sg_vpc_id}"
93
+ )
94
+
95
+ log.info("✓ All %d security groups are in VPC: %s", len(security_groups), vpc_id)
96
+
97
+ except ClientError as e:
98
+ if e.response["Error"]["Code"] == "InvalidGroup.NotFound":
99
+ raise ValueError(f"One or more security group IDs not found: {security_groups}") from e
100
+ raise ValueError(f"Failed to validate security groups: {e}") from e
101
+
102
+ log.info("✓ VPC configuration validated successfully")
103
+
104
+
105
+ def _ensure_network_service_linked_role(session: boto3.Session, logger) -> None:
106
+ """Ensure the AgentCore Network service-linked role exists."""
107
+ iam_client = session.client("iam")
108
+ role_name = "AWSServiceRoleForBedrockAgentCoreNetwork"
109
+
110
+ try:
111
+ # Check if role exists
112
+ iam_client.get_role(RoleName=role_name)
113
+ logger.info("✓ VPC service-linked role verified: %s", role_name)
114
+
115
+ except ClientError as e:
116
+ if e.response["Error"]["Code"] != "NoSuchEntity":
117
+ raise
118
+
119
+ logger.info("Creating VPC service-linked role...")
120
+
121
+ try:
122
+ iam_client.create_service_linked_role(
123
+ AWSServiceName="network.bedrock-agentcore.amazonaws.com",
124
+ Description="Service-linked role for Amazon Bedrock AgentCore VPC networking",
125
+ )
126
+ logger.info("✓ VPC service-linked role created: %s", role_name)
127
+
128
+ # Wait for propagation
129
+ import time
130
+
131
+ logger.info(" Waiting 10 seconds for IAM propagation...")
132
+ time.sleep(10)
133
+
134
+ except ClientError as e:
135
+ if e.response["Error"]["Code"] == "InvalidInput":
136
+ logger.info("✓ VPC service-linked role verified (created by another process)")
137
+ else:
138
+ logger.error("✗ Failed to create service-linked role: %s", e)
139
+ raise
140
+
141
+
28
142
  def _ensure_ecr_repository(agent_config, project_config, config_path, agent_name, region):
29
143
  """Ensure ECR repository exists (idempotent)."""
30
144
  ecr_uri = agent_config.aws.ecr_repository
@@ -136,6 +250,7 @@ def _ensure_memory_for_agent(
136
250
  project_config: BedrockAgentCoreConfigSchema,
137
251
  config_path: Path,
138
252
  agent_name: str,
253
+ console: Optional[Console] = None,
139
254
  ) -> Optional[str]:
140
255
  """Ensure memory resource exists for agent. Returns memory_id or None.
141
256
 
@@ -161,7 +276,10 @@ def _ensure_memory_for_agent(
161
276
  from ...operations.memory.constants import StrategyType
162
277
  from ...operations.memory.manager import MemoryManager
163
278
 
164
- memory_manager = MemoryManager(region_name=agent_config.aws.region)
279
+ memory_manager = MemoryManager(
280
+ region_name=agent_config.aws.region,
281
+ console=console, # ADD THIS
282
+ )
165
283
  memory_name = f"{agent_name}_mem" # Short name under 48 char limit
166
284
 
167
285
  # Check if memory already exists in cloud
@@ -190,6 +308,7 @@ def _ensure_memory_for_agent(
190
308
  # If LTM is enabled but no strategies exist, add them
191
309
  if agent_config.memory.has_ltm and len(existing_strategies) == 0:
192
310
  log.info("Adding LTM strategies to existing memory...")
311
+ console.print("⏳ Adding long-term memory strategies (this may take 30-180 seconds)...")
193
312
  memory_manager.update_memory_strategies_and_wait(
194
313
  memory_id=existing_memory.id,
195
314
  add_strategies=[
@@ -212,13 +331,21 @@ def _ensure_memory_for_agent(
212
331
  }
213
332
  },
214
333
  ],
215
- max_wait=30,
334
+ max_wait=300, # CHANGE: Increased from 30 to 300
216
335
  poll_interval=5,
217
336
  )
218
337
  memory = existing_memory
219
338
  log.info("✅ LTM strategies added to existing memory")
220
339
  else:
221
- memory = existing_memory
340
+ # CHANGE: ADD THIS BLOCK - Wait for existing memory to become ACTIVE
341
+ console.print("⏳ Waiting for existing memory to become ACTIVE...")
342
+ memory = memory_manager._wait_for_memory_active(
343
+ existing_memory.id,
344
+ max_wait=300,
345
+ poll_interval=5,
346
+ )
347
+ # END CHANGE
348
+
222
349
  if agent_config.memory.has_ltm and len(existing_strategies) > 0:
223
350
  log.info("✅ Using existing memory with %d strategies", len(existing_strategies))
224
351
  else:
@@ -251,28 +378,29 @@ def _ensure_memory_for_agent(
251
378
  else:
252
379
  log.info("Creating new STM-only memory...")
253
380
 
254
- # Use private method to avoid waiting
255
- memory = memory_manager._create_memory(
381
+ # CHANGE: Use create_memory_and_wait instead of _create_memory
382
+ console.print("⏳ Creating memory resource (this may take 30-180 seconds)...")
383
+ memory = memory_manager.create_memory_and_wait(
256
384
  name=memory_name,
257
385
  description=f"Memory for agent {agent_name} with {'STM+LTM' if strategies else 'STM only'}",
258
386
  strategies=strategies,
259
387
  event_expiry_days=agent_config.memory.event_expiry_days or 30,
260
- memory_execution_role_arn=None,
388
+ max_wait=300, # 5 minutes
389
+ poll_interval=5,
261
390
  )
391
+ log.info("✅ Memory created and active: %s", memory.id)
392
+ # END CHANGE
262
393
 
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)
394
+ # CHANGE: ADD THIS - Mark as created by toolkit since we just created it
265
395
  if not agent_config.memory.was_created_by_toolkit:
266
- log.warning("Memory created but flag was False - correcting to True")
267
396
  agent_config.memory.was_created_by_toolkit = True
268
-
269
- log.info("✅ New memory created: %s (provisioning in background)", memory.id)
397
+ # END CHANGE
270
398
 
271
399
  # Save memory configuration (preserving was_created_by_toolkit flag)
272
400
  agent_config.memory.memory_id = memory.id
273
401
  agent_config.memory.memory_arn = memory.arn
274
402
  agent_config.memory.memory_name = memory_name
275
- agent_config.memory.first_invoke_memory_check_done = False
403
+ agent_config.memory.first_invoke_memory_check_done = True # CHANGE: Set to True since memory is now ACTIVE
276
404
 
277
405
  project_config.agents[agent_config.name] = agent_config
278
406
  save_config(project_config, config_path)
@@ -303,8 +431,8 @@ def _deploy_to_bedrock_agentcore(
303
431
  if env_vars is None:
304
432
  env_vars = {}
305
433
 
306
- # Add memory configuration to env_vars if it exists
307
- if agent_config.memory and agent_config.memory.memory_id:
434
+ # Add memory configuration to env_vars only if memory is enabled
435
+ if agent_config.memory and agent_config.memory.mode != "NO_MEMORY" and agent_config.memory.memory_id:
308
436
  env_vars["BEDROCK_AGENTCORE_MEMORY_ID"] = agent_config.memory.memory_id
309
437
  env_vars["BEDROCK_AGENTCORE_MEMORY_NAME"] = agent_config.memory.memory_name
310
438
  log.info("Passing memory configuration to agent: %s", agent_config.memory.memory_id)
@@ -315,6 +443,15 @@ def _deploy_to_bedrock_agentcore(
315
443
  network_config = agent_config.aws.network_configuration.to_aws_dict()
316
444
  protocol_config = agent_config.aws.protocol_configuration.to_aws_dict()
317
445
 
446
+ lifecycle_config = None
447
+ if agent_config.aws.lifecycle_configuration.has_custom_settings:
448
+ lifecycle_config = agent_config.aws.lifecycle_configuration.to_aws_dict()
449
+ log.info(
450
+ "Applying custom lifecycle settings: idle=%s, max=%s",
451
+ agent_config.aws.lifecycle_configuration.idle_runtime_session_timeout,
452
+ agent_config.aws.lifecycle_configuration.max_lifetime,
453
+ )
454
+
318
455
  # Execution role should be available by now (either provided or auto-created)
319
456
  if not agent_config.aws.execution_role:
320
457
  raise ValueError(
@@ -340,6 +477,7 @@ def _deploy_to_bedrock_agentcore(
340
477
  protocol_config=protocol_config,
341
478
  env_vars=env_vars,
342
479
  auto_update_on_conflict=auto_update_on_conflict,
480
+ lifecycle_config=lifecycle_config,
343
481
  )
344
482
  break # Success! Exit retry loop
345
483
 
@@ -414,9 +552,45 @@ def _deploy_to_bedrock_agentcore(
414
552
  result = bedrock_agentcore_client.wait_for_agent_endpoint_ready(agent_id)
415
553
  log.info("Agent endpoint: %s", result)
416
554
 
555
+ if agent_config.aws.network_configuration.network_mode == "VPC":
556
+ vpc_subnets = agent_config.aws.network_configuration.network_mode_config.subnets
557
+ session = boto3.Session(region_name=region)
558
+ _check_vpc_deployment(session, agent_id, vpc_subnets, region)
559
+
417
560
  return agent_id, agent_arn
418
561
 
419
562
 
563
+ def _check_vpc_deployment(session: boto3.Session, agent_id: str, vpc_subnets: List[str], region: str) -> None:
564
+ """Verify VPC deployment created ENIs in the specified subnets."""
565
+ ec2_client = session.client("ec2", region_name=region)
566
+
567
+ try:
568
+ # Look for ENIs in our subnets with AgentCore description
569
+ response = ec2_client.describe_network_interfaces(
570
+ Filters=[
571
+ {"Name": "subnet-id", "Values": vpc_subnets},
572
+ {"Name": "description", "Values": ["*AgentCore*", "*bedrock-agentcore*"]},
573
+ ]
574
+ )
575
+
576
+ all_enis = response.get("NetworkInterfaces", [])
577
+ our_enis = [eni for eni in all_enis if eni.get("SubnetId") in vpc_subnets]
578
+
579
+ if our_enis:
580
+ log.info("✓ Found %d ENI(s) in configured subnets:", len(our_enis))
581
+ for eni in our_enis:
582
+ log.info(" - ENI ID: %s", eni["NetworkInterfaceId"])
583
+ log.info(" Subnet: %s", eni["SubnetId"])
584
+ log.info(" Private IP: %s", eni.get("PrivateIpAddress", "N/A"))
585
+ log.info(" Status: %s", eni["Status"])
586
+ log.info(" Security Groups: %s", [sg["GroupId"] for sg in eni.get("Groups", [])])
587
+ else:
588
+ log.info(":information_source: VPC network interfaces will be created on first invocation")
589
+
590
+ except Exception as e:
591
+ log.error("Error checking ENIs: %s", e)
592
+
593
+
420
594
  def launch_bedrock_agentcore(
421
595
  config_path: Path,
422
596
  agent_name: Optional[str] = None,
@@ -424,6 +598,7 @@ def launch_bedrock_agentcore(
424
598
  use_codebuild: bool = True,
425
599
  env_vars: Optional[dict] = None,
426
600
  auto_update_on_conflict: bool = False,
601
+ console: Optional[Console] = None,
427
602
  ) -> LaunchResult:
428
603
  """Launch Bedrock AgentCore locally or to cloud.
429
604
 
@@ -434,10 +609,14 @@ def launch_bedrock_agentcore(
434
609
  use_codebuild: Whether to use CodeBuild for ARM64 builds
435
610
  env_vars: Environment variables to pass to local container (dict of key-value pairs)
436
611
  auto_update_on_conflict: Whether to automatically update when agent already exists (default: False)
612
+ console: Optional Rich Console instance for progress output. Used to maintain
613
+ output hierarchy with CLI status contexts.
437
614
 
438
615
  Returns:
439
616
  LaunchResult model with launch details
440
617
  """
618
+ if console is None:
619
+ console = Console()
441
620
  # Load project configuration
442
621
  project_config = load_config(config_path)
443
622
  agent_config = project_config.get_agent_config(agent_name)
@@ -445,10 +624,25 @@ def launch_bedrock_agentcore(
445
624
  if env_vars is None:
446
625
  env_vars = {}
447
626
 
627
+ if agent_config.aws.network_configuration.network_mode == "VPC":
628
+ if local:
629
+ log.warning("⚠️ VPC configuration detected but running in local mode. VPC settings will be ignored.")
630
+ else:
631
+ log.info("Validating VPC resources...")
632
+ session = boto3.Session(region_name=agent_config.aws.region)
633
+ _validate_vpc_resources(session, agent_config, agent_config.aws.region)
634
+
635
+ # Ensure service-linked role exists for VPC networking
636
+ _ensure_network_service_linked_role(session, log)
637
+
448
638
  # Ensure memory exists for non-CodeBuild paths
449
639
  if not use_codebuild:
450
640
  _ensure_memory_for_agent(agent_config, project_config, config_path, agent_config.name)
451
641
 
642
+ # Ensure memory exists for non-CodeBuild paths
643
+ if not use_codebuild:
644
+ _ensure_memory_for_agent(agent_config, project_config, config_path, agent_config.name, console=console)
645
+
452
646
  # Add memory configuration to environment variables if available
453
647
  if agent_config.memory and agent_config.memory.memory_id:
454
648
  env_vars["BEDROCK_AGENTCORE_MEMORY_ID"] = agent_config.memory.memory_id
@@ -705,10 +899,13 @@ def _launch_with_codebuild(
705
899
  project_config,
706
900
  auto_update_on_conflict: bool = False,
707
901
  env_vars: Optional[dict] = None,
902
+ console: Optional[Console] = None,
708
903
  ) -> LaunchResult:
709
904
  """Launch using CodeBuild for ARM64 builds."""
905
+ if console is None:
906
+ console = Console()
710
907
  # Create memory if configured
711
- _ensure_memory_for_agent(agent_config, project_config, config_path, agent_name)
908
+ _ensure_memory_for_agent(agent_config, project_config, config_path, agent_name, console=console)
712
909
 
713
910
  # Execute shared CodeBuild workflow with full deployment mode
714
911
  build_id, ecr_uri, region, account_id = _execute_codebuild_workflow(
@@ -22,6 +22,10 @@ class ConfigureResult(BaseModel):
22
22
  ecr_repository: Optional[str] = Field(None, description="ECR repository URI")
23
23
  auto_create_ecr: bool = Field(False, description="Whether ECR will be auto-created")
24
24
  memory_id: Optional[str] = Field(default=None, description="Memory resource ID if created")
25
+ network_mode: Optional[str] = Field(None, description="Network mode (PUBLIC or VPC)")
26
+ network_subnets: Optional[List[str]] = Field(None, description="VPC subnet IDs")
27
+ network_security_groups: Optional[List[str]] = Field(None, description="VPC security group IDs")
28
+ network_vpc_id: Optional[str] = Field(None, description="VPC ID")
25
29
 
26
30
 
27
31
  # Launch operation models
@@ -70,12 +74,18 @@ class StatusConfigInfo(BaseModel):
70
74
  ecr_repository: Optional[str] = Field(None, description="ECR repository URI")
71
75
  agent_id: Optional[str] = Field(None, description="BedrockAgentCore agent ID")
72
76
  agent_arn: Optional[str] = Field(None, description="BedrockAgentCore agent ARN")
77
+ network_mode: Optional[str] = None
78
+ network_subnets: Optional[List[str]] = None
79
+ network_security_groups: Optional[List[str]] = None
80
+ network_vpc_id: Optional[str] = None
73
81
  memory_id: Optional[str] = Field(None, description="Memory resource ID")
74
82
  memory_status: Optional[str] = Field(None, description="Memory provisioning status (CREATING/ACTIVE/FAILED)")
75
83
  memory_type: Optional[str] = Field(None, description="Memory type (STM or STM+LTM)")
76
84
  memory_enabled: Optional[bool] = Field(None, description="Whether memory is enabled")
77
85
  memory_strategies: Optional[List[str]] = Field(None, description="Active memory strategies")
78
86
  memory_details: Optional[Dict[str, Any]] = Field(None, description="Detailed memory resource information")
87
+ idle_timeout: Optional[int] = Field(None, description="Idle runtime session timeout in seconds")
88
+ max_lifetime: Optional[int] = Field(None, description="Maximum instance lifetime in seconds")
79
89
 
80
90
 
81
91
  class StatusResult(BaseModel):
@@ -94,3 +104,12 @@ class DestroyResult(BaseModel):
94
104
  warnings: List[str] = Field(default_factory=list, description="List of warnings during destruction")
95
105
  errors: List[str] = Field(default_factory=list, description="List of errors during destruction")
96
106
  dry_run: bool = Field(default=False, description="Whether this was a dry run")
107
+
108
+
109
+ class StopSessionResult(BaseModel):
110
+ """Result of stop session operation."""
111
+
112
+ session_id: str = Field(..., description="Session ID that was stopped")
113
+ agent_name: str = Field(..., description="Name of the agent")
114
+ status_code: int = Field(..., description="HTTP status code of the operation")
115
+ message: str = Field(default="Session stopped successfully", description="Result message")
@@ -26,6 +26,24 @@ def get_status(config_path: Path, agent_name: Optional[str] = None) -> StatusRes
26
26
  project_config = load_config(config_path)
27
27
  agent_config = project_config.get_agent_config(agent_name)
28
28
 
29
+ # ADD NETWORK CONFIGURATION EXTRACTION
30
+ network_mode = agent_config.aws.network_configuration.network_mode
31
+ vpc_id = None
32
+
33
+ if network_mode == "VPC" and agent_config.aws.network_configuration.network_mode_config:
34
+ network_config = agent_config.aws.network_configuration.network_mode_config
35
+
36
+ # Try to get VPC ID from subnets (best effort - don't fail if can't retrieve)
37
+ try:
38
+ import boto3
39
+
40
+ ec2_client = boto3.client("ec2", region_name=agent_config.aws.region)
41
+ subnet_response = ec2_client.describe_subnets(SubnetIds=network_config.subnets[:1])
42
+ if subnet_response["Subnets"]:
43
+ vpc_id = subnet_response["Subnets"][0]["VpcId"]
44
+ except Exception:
45
+ pass # nosec B110 # Ignore errors - VPC ID is nice-to-have
46
+
29
47
  # Build config info
30
48
  config_info = StatusConfigInfo(
31
49
  name=agent_config.name,
@@ -36,8 +54,20 @@ def get_status(config_path: Path, agent_name: Optional[str] = None) -> StatusRes
36
54
  ecr_repository=agent_config.aws.ecr_repository,
37
55
  agent_id=agent_config.bedrock_agentcore.agent_id,
38
56
  agent_arn=agent_config.bedrock_agentcore.agent_arn,
57
+ network_mode=agent_config.aws.network_configuration.network_mode,
58
+ network_subnets=agent_config.aws.network_configuration.network_mode_config.subnets
59
+ if agent_config.aws.network_configuration.network_mode_config
60
+ else None,
61
+ network_security_groups=agent_config.aws.network_configuration.network_mode_config.security_groups
62
+ if agent_config.aws.network_configuration.network_mode_config
63
+ else None,
64
+ network_vpc_id=vpc_id,
39
65
  )
40
66
 
67
+ if agent_config.aws.lifecycle_configuration.has_custom_settings:
68
+ config_info.idle_timeout = agent_config.aws.lifecycle_configuration.idle_runtime_session_timeout
69
+ config_info.max_lifetime = agent_config.aws.lifecycle_configuration.max_lifetime
70
+
41
71
  # Check if memory is disabled first
42
72
  if agent_config.memory and agent_config.memory.mode == "NO_MEMORY":
43
73
  config_info.memory_type = "Disabled"
@@ -0,0 +1,123 @@
1
+ """Stop session operation - terminates active runtime sessions."""
2
+
3
+ import logging
4
+ from pathlib import Path
5
+ from typing import Optional
6
+
7
+ from botocore.exceptions import ClientError
8
+
9
+ from ...services.runtime import BedrockAgentCoreClient
10
+ from ...utils.runtime.config import load_config, save_config
11
+ from ...utils.runtime.schema import BedrockAgentCoreAgentSchema, BedrockAgentCoreConfigSchema
12
+ from .models import StopSessionResult
13
+
14
+ log = logging.getLogger(__name__)
15
+
16
+
17
+ def stop_runtime_session(
18
+ config_path: Path,
19
+ session_id: Optional[str] = None,
20
+ agent_name: Optional[str] = None,
21
+ ) -> StopSessionResult:
22
+ """Stop an active runtime session.
23
+
24
+ Args:
25
+ config_path: Path to BedrockAgentCore configuration file
26
+ session_id: Session ID to stop (if None, uses tracked session from config)
27
+ agent_name: Name of agent (for project configurations)
28
+
29
+ Returns:
30
+ StopSessionResult with operation details
31
+
32
+ Raises:
33
+ ValueError: If no session ID provided or found, or agent not deployed
34
+ FileNotFoundError: If configuration file doesn't exist
35
+ """
36
+ # Load project configuration
37
+ project_config = load_config(config_path)
38
+ agent_config = project_config.get_agent_config(agent_name)
39
+
40
+ log.info("Stopping session for agent: %s", agent_config.name)
41
+
42
+ # Check if agent is deployed
43
+ if not agent_config.bedrock_agentcore.agent_arn:
44
+ raise ValueError(
45
+ f"Agent '{agent_config.name}' is not deployed. Run 'agentcore launch' to deploy the agent first."
46
+ )
47
+
48
+ # Determine session ID to stop
49
+ target_session_id = session_id
50
+ if not target_session_id:
51
+ # Try to use tracked session from config
52
+ target_session_id = agent_config.bedrock_agentcore.agent_session_id
53
+ if not target_session_id:
54
+ raise ValueError(
55
+ "No active session found. Please provide --session-id or invoke the agent first to create a session."
56
+ )
57
+ log.info("Using tracked session ID from config: %s", target_session_id)
58
+ else:
59
+ log.info("Using provided session ID: %s", target_session_id)
60
+
61
+ region = agent_config.aws.region
62
+ agent_arn = agent_config.bedrock_agentcore.agent_arn
63
+
64
+ # Stop the session
65
+ client = BedrockAgentCoreClient(region)
66
+
67
+ try:
68
+ response = client.stop_runtime_session(
69
+ agent_arn=agent_arn,
70
+ session_id=target_session_id,
71
+ )
72
+
73
+ status_code = response.get("statusCode", 200)
74
+
75
+ # Success case
76
+ log.info("Session stopped successfully: %s", target_session_id)
77
+
78
+ # Clear the session ID from config if it matches
79
+ if agent_config.bedrock_agentcore.agent_session_id == target_session_id:
80
+ _clear_session_from_config(agent_config, project_config, config_path)
81
+
82
+ return StopSessionResult(
83
+ session_id=target_session_id,
84
+ agent_name=agent_config.name,
85
+ status_code=status_code,
86
+ message="Session stopped successfully",
87
+ )
88
+
89
+ except ClientError as e:
90
+ # Case 2: Error propagated as ClientError (defense in depth)
91
+ error_code = e.response.get("Error", {}).get("Code", "")
92
+ error_message = e.response.get("Error", {}).get("Message", "")
93
+ status_code = e.response.get("ResponseMetadata", {}).get("HTTPStatusCode", 500)
94
+
95
+ if error_code in ["ResourceNotFoundException", "NotFound"]:
96
+ log.warning("Session not found (may have already been terminated): %s", target_session_id)
97
+
98
+ # Still clear from config if it matches
99
+ if agent_config.bedrock_agentcore.agent_session_id == target_session_id:
100
+ _clear_session_from_config(agent_config, project_config, config_path)
101
+
102
+ return StopSessionResult(
103
+ session_id=target_session_id,
104
+ agent_name=agent_config.name,
105
+ status_code=404,
106
+ message="Session not found (may have already been terminated)",
107
+ )
108
+ else:
109
+ # Re-raise other client errors
110
+ log.error("Failed to stop session %s: %s - %s", target_session_id, error_code, error_message)
111
+ raise
112
+
113
+
114
+ def _clear_session_from_config(
115
+ agent_config: BedrockAgentCoreAgentSchema,
116
+ project_config: BedrockAgentCoreConfigSchema,
117
+ config_path: Path,
118
+ ) -> None:
119
+ """Clear session ID from agent configuration."""
120
+ agent_config.bedrock_agentcore.agent_session_id = None
121
+ project_config.agents[agent_config.name] = agent_config
122
+ save_config(project_config, config_path)
123
+ log.info("Cleared session ID from configuration")