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
@@ -4,11 +4,13 @@ import logging
4
4
  import os
5
5
  import re
6
6
  from pathlib import Path
7
- from typing import Any, Dict, Literal, Optional, Tuple
7
+ from typing import Any, Dict, List, Literal, Optional, Tuple
8
+
9
+ import boto3
8
10
 
9
11
  from ...cli.runtime.configuration_manager import ConfigurationManager
10
12
  from ...services.ecr import get_account_id, get_region
11
- from ...utils.runtime.config import merge_agent_config, save_config
13
+ from ...utils.runtime.config import load_config_if_exists, merge_agent_config, save_config
12
14
  from ...utils.runtime.container import ContainerRuntime
13
15
  from ...utils.runtime.entrypoint import detect_dependencies
14
16
  from ...utils.runtime.schema import (
@@ -16,8 +18,10 @@ from ...utils.runtime.schema import (
16
18
  BedrockAgentCoreAgentSchema,
17
19
  BedrockAgentCoreDeploymentInfo,
18
20
  CodeBuildConfig,
21
+ LifecycleConfiguration,
19
22
  MemoryConfig,
20
23
  NetworkConfiguration,
24
+ NetworkModeConfig,
21
25
  ObservabilityConfig,
22
26
  ProtocolConfiguration,
23
27
  )
@@ -133,7 +137,7 @@ def configure_bedrock_agentcore(
133
137
  auto_create_ecr: bool = True,
134
138
  auto_create_execution_role: bool = True,
135
139
  enable_observability: bool = True,
136
- memory_mode: Literal["NO_MEMORY", "STM_ONLY", "STM_AND_LTM"] = "STM_ONLY",
140
+ memory_mode: Literal["NO_MEMORY", "STM_ONLY", "STM_AND_LTM"] = "NO_MEMORY",
137
141
  requirements_file: Optional[str] = None,
138
142
  authorizer_configuration: Optional[Dict[str, Any]] = None,
139
143
  request_header_configuration: Optional[Dict[str, Any]] = None,
@@ -142,6 +146,11 @@ def configure_bedrock_agentcore(
142
146
  protocol: Optional[str] = None,
143
147
  non_interactive: bool = False,
144
148
  source_path: Optional[str] = None,
149
+ vpc_enabled: bool = False,
150
+ vpc_subnets: Optional[List[str]] = None,
151
+ vpc_security_groups: Optional[List[str]] = None,
152
+ idle_timeout: Optional[int] = None,
153
+ max_lifetime: Optional[int] = None,
145
154
  ) -> ConfigureResult:
146
155
  """Configure Bedrock AgentCore application with deployment settings.
147
156
 
@@ -164,6 +173,13 @@ def configure_bedrock_agentcore(
164
173
  protocol: agent server protocol, must be either HTTP or MCP or A2A
165
174
  non_interactive: Skip interactive prompts and use defaults
166
175
  source_path: Optional path to agent source code directory
176
+ vpc_enabled: Whether to enable VPC networking mode
177
+ vpc_subnets: List of subnet IDs for VPC mode
178
+ vpc_security_groups: List of security group IDs for VPC mode
179
+ idle_timeout: Idle runtime session timeout in seconds (60-28800).
180
+ If not specified, AWS API default (900s / 15 minutes) is used.
181
+ max_lifetime: Maximum instance lifetime in seconds (60-28800).
182
+ If not specified, AWS API default (28800s / 8 hours) is used.
167
183
 
168
184
  Returns:
169
185
  ConfigureResult model with configuration details
@@ -244,7 +260,7 @@ def configure_bedrock_agentcore(
244
260
  else: # STM_ONLY
245
261
  log.info("Memory configuration: Short-term memory only")
246
262
  else:
247
- # Interactive mode: prompt user (only if memory not explicitly disabled)
263
+ # Interactive mode - let user choose
248
264
  action, value = config_manager.prompt_memory_selection()
249
265
 
250
266
  if action == "USE_EXISTING":
@@ -274,6 +290,21 @@ def configure_bedrock_agentcore(
274
290
  memory_id = None
275
291
  memory_name = None
276
292
 
293
+ # Handle lifecycle configuration
294
+ lifecycle_config = LifecycleConfiguration()
295
+ if idle_timeout is not None or max_lifetime is not None:
296
+ lifecycle_config = LifecycleConfiguration(
297
+ idle_runtime_session_timeout=idle_timeout,
298
+ max_lifetime=max_lifetime,
299
+ )
300
+
301
+ if verbose:
302
+ log.debug("Lifecycle configuration:")
303
+ if idle_timeout:
304
+ log.debug(" Idle timeout: %ds (%d minutes)", idle_timeout, idle_timeout / 60)
305
+ if max_lifetime:
306
+ log.debug(" Max lifetime: %ds (%d hours)", max_lifetime, max_lifetime / 3600)
307
+
277
308
  if config_path.exists():
278
309
  try:
279
310
  from ...utils.runtime.config import load_config
@@ -305,6 +336,42 @@ def configure_bedrock_agentcore(
305
336
  if verbose and execution_role_arn:
306
337
  log.debug("Using same role for CodeBuild: %s", codebuild_execution_role_arn)
307
338
 
339
+ if vpc_enabled:
340
+ if not vpc_subnets or not vpc_security_groups:
341
+ raise ValueError("VPC mode requires both subnets and security groups")
342
+
343
+ for subnet_id in vpc_subnets:
344
+ if not subnet_id.startswith("subnet-"):
345
+ raise ValueError(
346
+ f"Invalid subnet ID format: {subnet_id}\nSubnet IDs must start with 'subnet-' (e.g., subnet-abc123)"
347
+ )
348
+ if len(subnet_id) < 15: # "subnet-" (7) + 8 chars = 15
349
+ raise ValueError(
350
+ f"Invalid subnet ID format: {subnet_id}\nSubnet ID is too short. Expected format: subnet-xxxxxxxx"
351
+ )
352
+
353
+ # Validate security group IDs format
354
+ for sg_id in vpc_security_groups:
355
+ if not sg_id.startswith("sg-"):
356
+ raise ValueError(
357
+ f"Invalid security group ID format: {sg_id}\n"
358
+ f"Security group IDs must start with 'sg-' (e.g., sg-abc123)"
359
+ )
360
+ if len(sg_id) < 11: # "sg-" (3) + 8 chars = 11
361
+ raise ValueError(
362
+ f"Invalid security group ID format: {sg_id}\n"
363
+ f"Security group ID is too short. Expected format: sg-xxxxxxxx"
364
+ )
365
+
366
+ network_config = NetworkConfiguration(
367
+ network_mode="VPC",
368
+ network_mode_config=NetworkModeConfig(subnets=vpc_subnets, security_groups=vpc_security_groups),
369
+ )
370
+ log.info("Network mode: VPC with %d subnets and %d security groups", len(vpc_subnets), len(vpc_security_groups))
371
+ else:
372
+ network_config = NetworkConfiguration(network_mode="PUBLIC")
373
+ log.info("Network mode: PUBLIC")
374
+
308
375
  # Generate Dockerfile and .dockerignore
309
376
  bedrock_agentcore_name = None
310
377
  # Try to find the variable name for the Bedrock AgentCore instance in the file
@@ -332,6 +399,11 @@ def configure_bedrock_agentcore(
332
399
  else:
333
400
  dockerfile_output_dir = build_dir
334
401
 
402
+ if memory_config.mode == "NO_MEMORY":
403
+ memory_id = None
404
+ memory_name = None
405
+ log.debug("Cleared memory_id/name for Dockerfile generation (memory disabled)")
406
+
335
407
  # Generate Dockerfile in the correct location (no moving needed)
336
408
  dockerfile_path = runtime.generate_dockerfile(
337
409
  entrypoint_path,
@@ -374,6 +446,32 @@ def configure_bedrock_agentcore(
374
446
  log.debug("Agent name from BedrockAgentCoreApp: %s", agent_name)
375
447
  log.debug("Config path: %s", config_path)
376
448
 
449
+ existing_project_config = load_config_if_exists(config_path)
450
+
451
+ if existing_project_config and agent_name in existing_project_config.agents:
452
+ existing_agent = existing_project_config.agents[agent_name]
453
+ existing_network = existing_agent.aws.network_configuration
454
+
455
+ # Import validation helper
456
+ from .vpc_validation import check_network_immutability
457
+
458
+ # Check if network config is being changed
459
+ error = check_network_immutability(
460
+ existing_network_mode=existing_network.network_mode,
461
+ existing_subnets=existing_network.network_mode_config.subnets
462
+ if existing_network.network_mode_config
463
+ else None,
464
+ existing_security_groups=existing_network.network_mode_config.security_groups
465
+ if existing_network.network_mode_config
466
+ else None,
467
+ new_network_mode="VPC" if vpc_enabled else "PUBLIC",
468
+ new_subnets=vpc_subnets,
469
+ new_security_groups=vpc_security_groups,
470
+ )
471
+
472
+ if error:
473
+ raise ValueError(error)
474
+
377
475
  # Convert to POSIX for cross-platform compatibility
378
476
  entrypoint_path_str = entrypoint_path.as_posix()
379
477
 
@@ -418,9 +516,10 @@ def configure_bedrock_agentcore(
418
516
  region=region,
419
517
  ecr_repository=ecr_repository,
420
518
  ecr_auto_create=ecr_auto_create_value,
421
- network_configuration=NetworkConfiguration(network_mode="PUBLIC"),
519
+ network_configuration=network_config,
422
520
  protocol_configuration=ProtocolConfiguration(server_protocol=protocol or "HTTP"),
423
521
  observability=ObservabilityConfig(enabled=enable_observability),
522
+ lifecycle_configuration=lifecycle_config,
424
523
  ),
425
524
  bedrock_agentcore=BedrockAgentCoreDeploymentInfo(),
426
525
  codebuild=CodeBuildConfig(
@@ -438,6 +537,18 @@ def configure_bedrock_agentcore(
438
537
  if verbose:
439
538
  log.debug("Configuration saved with agent: %s", agent_name)
440
539
 
540
+ # Get VPC ID for display if VPC mode
541
+ vpc_id = None
542
+ if vpc_enabled and vpc_subnets:
543
+ try:
544
+ session = boto3.Session(region_name=region)
545
+ ec2_client = session.client("ec2", region_name=region)
546
+ subnet_response = ec2_client.describe_subnets(SubnetIds=[vpc_subnets[0]])
547
+ if subnet_response["Subnets"]:
548
+ vpc_id = subnet_response["Subnets"][0]["VpcId"]
549
+ except Exception:
550
+ pass # nosec B110
551
+
441
552
  return ConfigureResult(
442
553
  config_path=config_path,
443
554
  dockerfile_path=dockerfile_path,
@@ -448,6 +559,10 @@ def configure_bedrock_agentcore(
448
559
  execution_role=execution_role_arn,
449
560
  ecr_repository=ecr_repository,
450
561
  auto_create_ecr=auto_create_ecr and not ecr_repository,
562
+ network_mode="VPC" if vpc_enabled else "PUBLIC",
563
+ network_subnets=vpc_subnets if vpc_enabled else None,
564
+ network_security_groups=vpc_security_groups if vpc_enabled else None,
565
+ network_vpc_id=vpc_id,
451
566
  )
452
567
 
453
568
 
@@ -7,6 +7,7 @@ from typing import Any, Optional
7
7
 
8
8
  from bedrock_agentcore.services.identity import IdentityClient
9
9
 
10
+ from ...operations.identity.oauth2_callback_server import WORKLOAD_USER_ID, BedrockAgentCoreIdentity3loCallback
10
11
  from ...services.runtime import BedrockAgentCoreClient, generate_session_id
11
12
  from ...utils.runtime.config import load_config, save_config
12
13
  from ...utils.runtime.schema import BedrockAgentCoreConfigSchema
@@ -30,59 +31,6 @@ def invoke_bedrock_agentcore(
30
31
  project_config = load_config(config_path)
31
32
  agent_config = project_config.get_agent_config(agent_name)
32
33
 
33
- # Check memory status on first invoke if memory is enabled (STM or LTM)
34
- if (
35
- agent_config.memory
36
- and agent_config.memory.mode != "NO_MEMORY"
37
- and agent_config.memory.memory_id
38
- and not agent_config.memory.first_invoke_memory_check_done
39
- ):
40
- try:
41
- from ...operations.memory.constants import MemoryStatus
42
- from ...operations.memory.manager import MemoryManager
43
-
44
- memory_manager = MemoryManager(region_name=agent_config.aws.region)
45
- memory_status = memory_manager.get_memory_status(agent_config.memory.memory_id)
46
-
47
- if memory_status != MemoryStatus.ACTIVE.value:
48
- # Determine memory type for better messaging
49
- memory_type = "Memory"
50
- if agent_config.memory.has_ltm:
51
- memory_type = "Long-term memory"
52
- time_estimate = "60-180 seconds"
53
- else:
54
- memory_type = "Short-term memory"
55
- time_estimate = "30-90 seconds"
56
-
57
- # Provide graceful error message
58
- error_message = (
59
- f"Memory is still provisioning (current status: {memory_status}). "
60
- f"{memory_type} takes {time_estimate} to activate.\n\n"
61
- f"Please wait and check status with:\n"
62
- f" agentcore status{f' --agent {agent_name}' if agent_name else ''}"
63
- )
64
-
65
- # Log the message for visibility
66
- log.warning("Memory not yet active for agent '%s': %s", agent_config.name, memory_status)
67
-
68
- raise ValueError(error_message)
69
-
70
- # Memory is active, mark check as done
71
- agent_config.memory.first_invoke_memory_check_done = True
72
- project_config.agents[agent_config.name] = agent_config
73
- save_config(project_config, config_path)
74
- log.info("Memory is active, proceeding with invoke")
75
-
76
- except ImportError as e:
77
- log.error("Failed to import MemoryManager: %s", e)
78
- # Continue without check if import fails
79
- except Exception as e:
80
- # If it's our ValueError, re-raise it
81
- if "Memory is still provisioning" in str(e):
82
- raise
83
- # For other errors, log but continue
84
- log.warning("Could not check memory status: %s", e)
85
-
86
34
  # Log which agent is being invoked
87
35
  mode = "locally" if local_mode else "via cloud endpoint"
88
36
  log.debug("Invoking BedrockAgentCore agent '%s' %s", agent_config.name, mode)
@@ -121,9 +69,19 @@ def invoke_bedrock_agentcore(
121
69
  workload_name=workload_name, user_token=bearer_token, user_id=user_id
122
70
  )["workloadAccessToken"]
123
71
 
72
+ agent_config.oauth_configuration[WORKLOAD_USER_ID] = user_id # type: ignore : populated by _get_workload_name(...)
73
+ save_config(project_config, config_path)
74
+
75
+ oauth2_callback_url = BedrockAgentCoreIdentity3loCallback.get_oauth2_callback_endpoint()
76
+ _update_workload_identity_with_oauth2_callback_url(
77
+ identity_client, workload_name=workload_name, oauth2_callback_url=oauth2_callback_url
78
+ )
79
+
124
80
  # TODO: store and read port config of local running container
125
81
  client = LocalBedrockAgentCoreClient("http://127.0.0.1:8080")
126
- response = client.invoke_endpoint(session_id, payload_str, workload_access_token, custom_headers)
82
+ response = client.invoke_endpoint(
83
+ session_id, payload_str, workload_access_token, oauth2_callback_url, custom_headers
84
+ )
127
85
 
128
86
  else:
129
87
  if not agent_arn:
@@ -163,6 +121,24 @@ def invoke_bedrock_agentcore(
163
121
  )
164
122
 
165
123
 
124
+ def _update_workload_identity_with_oauth2_callback_url(
125
+ identity_client: IdentityClient,
126
+ workload_name: str,
127
+ oauth2_callback_url: str,
128
+ ) -> None:
129
+ workload_identity = identity_client.get_workload_identity(name=workload_name)
130
+ allowed_resource_oauth_2_return_urls = workload_identity.get("allowedResourceOauth2ReturnUrls") or []
131
+ if oauth2_callback_url in allowed_resource_oauth_2_return_urls:
132
+ return
133
+
134
+ log.info("Updating workload %s with callback url %s", workload_name, oauth2_callback_url)
135
+
136
+ identity_client.update_workload_identity(
137
+ name=workload_name,
138
+ allowed_resource_oauth_2_return_urls=[*allowed_resource_oauth_2_return_urls, oauth2_callback_url],
139
+ )
140
+
141
+
166
142
  def _get_workload_name(
167
143
  project_config: BedrockAgentCoreConfigSchema,
168
144
  project_config_path: Path,