@aws/agentcore 1.0.0-preview.11 → 1.0.0-preview.13

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.
Files changed (76) hide show
  1. package/dist/agent-inspector/index.css +1 -1
  2. package/dist/agent-inspector/index.js +83 -78
  3. package/dist/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap +860 -158
  4. package/dist/assets/cdk/bin/cdk.ts +53 -5
  5. package/dist/assets/cdk/lib/cdk-stack.ts +164 -1
  6. package/dist/assets/cdk/package.json +1 -1
  7. package/dist/assets/cdk/test/cdk.test.ts +2 -0
  8. package/dist/assets/python/a2a/googleadk/base/main.py +24 -16
  9. package/dist/assets/python/a2a/langchain_langgraph/base/main.py +24 -16
  10. package/dist/assets/python/a2a/langchain_langgraph/base/pyproject.toml +1 -1
  11. package/dist/assets/python/a2a/strands/base/main.py +23 -17
  12. package/dist/assets/python/agui/googleadk/base/main.py +62 -2
  13. package/dist/assets/python/agui/googleadk/base/pyproject.toml +1 -1
  14. package/dist/assets/python/agui/langchain_langgraph/base/main.py +56 -0
  15. package/dist/assets/python/agui/langchain_langgraph/base/pyproject.toml +1 -1
  16. package/dist/assets/python/agui/strands/base/main.py +63 -1
  17. package/dist/assets/python/http/autogen/base/main.py +27 -19
  18. package/dist/assets/python/http/autogen/base/pyproject.toml +1 -1
  19. package/dist/assets/python/http/googleadk/base/main.py +24 -16
  20. package/dist/assets/python/http/langchain_langgraph/base/main.py +24 -16
  21. package/dist/assets/python/http/langchain_langgraph/base/pyproject.toml +1 -1
  22. package/dist/assets/python/http/openaiagents/base/main.py +24 -16
  23. package/dist/assets/python/http/openaiagents/base/pyproject.toml +1 -1
  24. package/dist/assets/python/http/strands/base/main.py +62 -19
  25. package/dist/assets/python/http/strands/capabilities/payments/__init__.py +4 -0
  26. package/dist/assets/python/http/strands/capabilities/payments/payments.py +142 -0
  27. package/dist/assets/python/mcp/standalone/base/main.py +58 -0
  28. package/dist/cli/index.mjs +772 -660
  29. package/dist/lib/errors/types.d.ts +3 -0
  30. package/dist/lib/errors/types.d.ts.map +1 -1
  31. package/dist/lib/errors/types.js +10 -1
  32. package/dist/lib/errors/types.js.map +1 -1
  33. package/dist/lib/errors/zod.js +1 -1
  34. package/dist/lib/errors/zod.js.map +1 -1
  35. package/dist/lib/packaging/helpers.d.ts.map +1 -1
  36. package/dist/lib/packaging/helpers.js +50 -10
  37. package/dist/lib/packaging/helpers.js.map +1 -1
  38. package/dist/lib/result.d.ts +6 -4
  39. package/dist/lib/result.d.ts.map +1 -1
  40. package/dist/lib/result.js +7 -0
  41. package/dist/lib/result.js.map +1 -1
  42. package/dist/lib/utils/env.d.ts +4 -0
  43. package/dist/lib/utils/env.d.ts.map +1 -1
  44. package/dist/lib/utils/env.js +18 -0
  45. package/dist/lib/utils/env.js.map +1 -1
  46. package/dist/lib/utils/index.d.ts +1 -1
  47. package/dist/lib/utils/index.d.ts.map +1 -1
  48. package/dist/lib/utils/index.js +2 -1
  49. package/dist/lib/utils/index.js.map +1 -1
  50. package/dist/schema/schemas/agent-env.d.ts +50 -4
  51. package/dist/schema/schemas/agent-env.d.ts.map +1 -1
  52. package/dist/schema/schemas/agent-env.js +102 -4
  53. package/dist/schema/schemas/agent-env.js.map +1 -1
  54. package/dist/schema/schemas/agentcore-project.d.ts +69 -4
  55. package/dist/schema/schemas/agentcore-project.d.ts.map +1 -1
  56. package/dist/schema/schemas/agentcore-project.js +61 -5
  57. package/dist/schema/schemas/agentcore-project.js.map +1 -1
  58. package/dist/schema/schemas/deployed-state.d.ts +97 -0
  59. package/dist/schema/schemas/deployed-state.d.ts.map +1 -1
  60. package/dist/schema/schemas/deployed-state.js +24 -4
  61. package/dist/schema/schemas/deployed-state.js.map +1 -1
  62. package/dist/schema/schemas/primitives/harness.d.ts +41 -0
  63. package/dist/schema/schemas/primitives/harness.d.ts.map +1 -1
  64. package/dist/schema/schemas/primitives/harness.js +65 -1
  65. package/dist/schema/schemas/primitives/harness.js.map +1 -1
  66. package/dist/schema/schemas/primitives/index.d.ts +4 -2
  67. package/dist/schema/schemas/primitives/index.d.ts.map +1 -1
  68. package/dist/schema/schemas/primitives/index.js +14 -2
  69. package/dist/schema/schemas/primitives/index.js.map +1 -1
  70. package/dist/schema/schemas/primitives/payment.d.ts +54 -0
  71. package/dist/schema/schemas/primitives/payment.d.ts.map +1 -0
  72. package/dist/schema/schemas/primitives/payment.js +79 -0
  73. package/dist/schema/schemas/primitives/payment.js.map +1 -0
  74. package/npm-shrinkwrap.json +1027 -706
  75. package/package.json +13 -7
  76. package/scripts/run-e2e-local.sh +9 -3
@@ -15,9 +15,12 @@ from mcp_client.client import get_streamable_http_mcp_client
15
15
  {{#if hasMemory}}
16
16
  from memory.session import get_memory_session_manager
17
17
  {{/if}}
18
- {{#if sessionStorageMountPath}}
18
+ {{#if needsOs}}
19
19
  import os
20
20
  {{/if}}
21
+ {{#if hasPayment}}
22
+ from capabilities.payments.payments import create_payments_plugin, PAYMENT_SYSTEM_PROMPT
23
+ {{/if}}
21
24
 
22
25
  app = BedrockAgentCoreApp()
23
26
  log = app.logger
@@ -31,9 +34,12 @@ mcp_clients = [get_streamable_http_mcp_client()]
31
34
 
32
35
  DEFAULT_SYSTEM_PROMPT = """
33
36
  You are a helpful assistant. Use tools when appropriate.
34
- {{#if sessionStorageMountPath}}
35
- You have persistent storage at {{sessionStorageMountPath}}. Use file tools to read and write files. Data persists across sessions.
36
- {{/if}}
37
+ {{#if needsOs}}
38
+ You have access to the following mounted filesystems. Use file_read, file_write, and list_files with full absolute paths:
39
+ {{#if sessionStorageMountPath}}- {{sessionStorageMountPath}}: ephemeral session storage (lost when session ends)
40
+ {{/if}}{{#each efsMounts}}- {{mountPath}}: EFS persistent storage (persists across sessions and agent restarts)
41
+ {{/each}}{{#each s3Mounts}}- {{mountPath}}: S3 Files persistent storage (durable, backed by S3)
42
+ {{/each}}{{/if}}
37
43
  """
38
44
 
39
45
  {{#if hasConfigBundle}}
@@ -54,19 +60,22 @@ def add_numbers(a: int, b: int) -> int:
54
60
  return a+b
55
61
  tools.append(add_numbers)
56
62
 
57
- {{#if sessionStorageMountPath}}
58
- SESSION_STORAGE_PATH = "{{sessionStorageMountPath}}"
63
+ {{#if needsOs}}
64
+ _MOUNT_PATHS = [
65
+ {{#if sessionStorageMountPath}}"{{sessionStorageMountPath}}",{{/if}}
66
+ {{#each efsMounts}}"{{mountPath}}",{{/each}}
67
+ {{#each s3Mounts}}"{{mountPath}}",{{/each}}
68
+ ]
59
69
 
60
70
  def _safe_resolve(path: str) -> str:
61
- """Resolve path safely within the storage boundary."""
62
- resolved = os.path.realpath(os.path.join(SESSION_STORAGE_PATH, path.lstrip("/")))
63
- if not resolved.startswith(os.path.realpath(SESSION_STORAGE_PATH)):
64
- raise ValueError(f"Path '{path}' is outside the storage boundary")
71
+ resolved = os.path.realpath(path)
72
+ if not any(resolved == os.path.realpath(m) or resolved.startswith(os.path.realpath(m) + os.sep) for m in _MOUNT_PATHS):
73
+ raise ValueError(f"Path '{path}' is not within any configured mount ({', '.join(_MOUNT_PATHS)})")
65
74
  return resolved
66
75
 
67
76
  @tool
68
77
  def file_read(path: str) -> str:
69
- """Read a file from persistent storage. The path is relative to the storage root."""
78
+ """Read a file from a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
70
79
  try:
71
80
  full_path = _safe_resolve(path)
72
81
  with open(full_path) as f:
@@ -78,7 +87,7 @@ def file_read(path: str) -> str:
78
87
 
79
88
  @tool
80
89
  def file_write(path: str, content: str) -> str:
81
- """Write content to a file in persistent storage. The path is relative to the storage root."""
90
+ """Write a file to a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
82
91
  try:
83
92
  full_path = _safe_resolve(path)
84
93
  parent = os.path.dirname(full_path)
@@ -93,16 +102,16 @@ def file_write(path: str, content: str) -> str:
93
102
  return f"Error writing '{path}': {e.strerror}"
94
103
 
95
104
  @tool
96
- def list_files(directory: str = "") -> str:
97
- """List files in persistent storage. The directory is relative to the storage root."""
105
+ def list_files(path: str) -> str:
106
+ """List files in a mounted filesystem directory. Use the absolute path (e.g. /mnt/tools)."""
98
107
  try:
99
- target = _safe_resolve(directory)
100
- entries = os.listdir(target)
108
+ full_path = _safe_resolve(path)
109
+ entries = os.listdir(full_path)
101
110
  return "\n".join(entries) if entries else "(empty directory)"
102
111
  except ValueError as e:
103
112
  return str(e)
104
113
  except OSError as e:
105
- return f"Error listing '{directory}': {e.strerror}"
114
+ return f"Error listing '{path}': {e.strerror}"
106
115
 
107
116
  tools.extend([file_read, file_write, list_files])
108
117
  {{/if}}
@@ -147,12 +156,12 @@ class ConfigBundleHook(HookProvider):
147
156
  {{/if}}
148
157
 
149
158
  {{#if hasMemory}}
159
+ {{#unless hasPayment}}
150
160
  def agent_factory():
151
161
  cache = {}
152
162
  def get_or_create_agent(session_id, user_id):
153
163
  key = f"{session_id}/{user_id}"
154
164
  if key not in cache:
155
- # Create an agent for the given session_id and user_id
156
165
  cache[key] = Agent(
157
166
  model=load_model(),
158
167
  session_manager=get_memory_session_manager(session_id, user_id),
@@ -163,6 +172,7 @@ def agent_factory():
163
172
  return cache[key]
164
173
  return get_or_create_agent
165
174
  get_or_create_agent = agent_factory()
175
+ {{/unless}}
166
176
  {{else}}
167
177
  {{#if hasConfigBundle}}
168
178
  def create_agent():
@@ -173,6 +183,7 @@ def create_agent():
173
183
  hooks=[ConfigBundleHook()],
174
184
  )
175
185
  {{else}}
186
+ {{#unless hasPayment}}
176
187
  _agent = None
177
188
 
178
189
  def get_or_create_agent():
@@ -181,9 +192,10 @@ def get_or_create_agent():
181
192
  _agent = Agent(
182
193
  model=load_model(),
183
194
  system_prompt=DEFAULT_SYSTEM_PROMPT,
184
- tools=tools
195
+ tools=tools,
185
196
  )
186
197
  return _agent
198
+ {{/unless}}
187
199
  {{/if}}
188
200
  {{/if}}
189
201
 
@@ -192,16 +204,47 @@ def get_or_create_agent():
192
204
  async def invoke(payload, context):
193
205
  log.info("Invoking Agent.....")
194
206
 
207
+ {{#if hasPayment}}
208
+ user_id = payload.get("user_id") or getattr(context, "user_id", "default-user")
209
+ instrument_id = payload.get("payment_instrument_id")
210
+ session_id = payload.get("payment_session_id")
211
+ payments_plugin = create_payments_plugin(user_id, instrument_id, session_id)
212
+ plugins = [payments_plugin] if payments_plugin else []
213
+ {{/if}}
214
+
195
215
  {{#if hasMemory}}
216
+ {{#if hasPayment}}
217
+ mem_session_id = getattr(context, 'session_id', 'default-session')
218
+ mem_user_id = getattr(context, 'user_id', 'default-user')
219
+ agent = Agent(
220
+ model=load_model(),
221
+ session_manager=get_memory_session_manager(mem_session_id, mem_user_id),
222
+ system_prompt=DEFAULT_SYSTEM_PROMPT + PAYMENT_SYSTEM_PROMPT,
223
+ tools=tools,
224
+ plugins=plugins,{{#if hasConfigBundle}}
225
+ hooks=[ConfigBundleHook()],{{/if}}
226
+ )
227
+ {{else}}
196
228
  session_id = getattr(context, 'session_id', 'default-session')
197
229
  user_id = getattr(context, 'user_id', 'default-user')
198
230
  agent = get_or_create_agent(session_id, user_id)
231
+ {{/if}}
232
+ {{else}}
233
+ {{#if hasPayment}}
234
+ agent = Agent(
235
+ model=load_model(),
236
+ system_prompt=DEFAULT_SYSTEM_PROMPT + PAYMENT_SYSTEM_PROMPT,
237
+ tools=tools,
238
+ plugins=plugins,{{#if hasConfigBundle}}
239
+ hooks=[ConfigBundleHook()],{{/if}}
240
+ )
199
241
  {{else}}
200
242
  {{#if hasConfigBundle}}
201
243
  agent = create_agent()
202
244
  {{else}}
203
245
  agent = get_or_create_agent()
204
246
  {{/if}}
247
+ {{/if}}
205
248
  {{/if}}
206
249
 
207
250
  # Execute and format response
@@ -0,0 +1,4 @@
1
+ """Payment capabilities for Strands agents."""
2
+ from .payments import create_payments_plugin
3
+
4
+ __all__ = ["create_payments_plugin"]
@@ -0,0 +1,142 @@
1
+ """Payment capability -- auto-generated by agentcore CLI.
2
+
3
+ Configures AgentCorePaymentsPlugin for Strands agents.
4
+ Manager config is auto-discovered from AGENTCORE_PAYMENT_*
5
+ environment variables set at deploy time.
6
+
7
+ Uses a per-invocation factory pattern so each request gets its own
8
+ plugin instance with the correct user_id, instrument_id, and session_id.
9
+ This prevents concurrency bugs where one user's payment context
10
+ could leak to another user's request.
11
+
12
+ Uses the official SDK plugin which handles:
13
+ - x402 v1 (body-based) and v2 (header-based) payment detection
14
+ - Automatic 402 response interception and payment processing
15
+ - Retry limiting (max 3 payment retries per tool use)
16
+ - Error management and logging
17
+ """
18
+ import os
19
+ import logging
20
+
21
+ import boto3
22
+ from bedrock_agentcore.payments.integrations.strands import AgentCorePaymentsPlugin
23
+ from bedrock_agentcore.payments.integrations.config import AgentCorePaymentsPluginConfig
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+ PAYMENT_SYSTEM_PROMPT = """
28
+ You have payment capabilities via the x402 protocol:
29
+ - Use http_request to call HTTP endpoints. 402 Payment Required responses are settled automatically by the plugin and the call is retried.
30
+ - Use get_payment_session to check your remaining budget before expensive operations
31
+ - Use get_payment_instrument_balance to check wallet USDC balance
32
+ - Use list_payment_instruments to see available payment instruments
33
+ - If budget is low, inform the user before proceeding with paid requests
34
+ """
35
+
36
+ _manager_arn = None
37
+ _connector_id = None
38
+ _process_payment_role_arn = None
39
+ _name_segment = None
40
+ _region = None
41
+ _auth_mode = None
42
+ _manager_count = 0
43
+ for key, value in os.environ.items():
44
+ if key.startswith("AGENTCORE_PAYMENT_") and key.endswith("_MANAGER_ARN"):
45
+ if _manager_arn is None:
46
+ _manager_arn = value
47
+ _name_segment = key[len("AGENTCORE_PAYMENT_"):-len("_MANAGER_ARN")]
48
+ _manager_count += 1
49
+ if _manager_count > 1:
50
+ logger.warning(
51
+ "Multiple payment managers detected in environment. Using the first one found. "
52
+ "Remove extra AGENTCORE_PAYMENT_*_MANAGER_ARN env vars to eliminate ambiguity."
53
+ )
54
+ _region = os.getenv("AWS_REGION")
55
+
56
+ _prefix = f"AGENTCORE_PAYMENT_{_name_segment}_" if _name_segment else "AGENTCORE_PAYMENT_"
57
+ _auth_mode = os.getenv(f"{_prefix}AUTH_MODE", "sigv4")
58
+ _connector_id = os.getenv(f"{_prefix}CONNECTOR_ID")
59
+ _process_payment_role_arn = os.getenv(f"{_prefix}PROCESS_PAYMENT_ROLE_ARN")
60
+ _auto_payment = os.getenv(f"{_prefix}AUTO_PAYMENT", "true").lower() == "true"
61
+ _allowlist_raw = os.getenv(f"{_prefix}TOOL_ALLOWLIST")
62
+ _allowlist = _allowlist_raw.split(",") if _allowlist_raw else None
63
+ _network_prefs_raw = os.getenv(f"{_prefix}NETWORK_PREFERENCES")
64
+ _network_prefs = _network_prefs_raw.split(",") if _network_prefs_raw else None
65
+
66
+ if not _manager_arn:
67
+ logger.warning("No payment manager config found in environment")
68
+ if not _connector_id:
69
+ logger.warning("No payment connector config found in environment")
70
+
71
+
72
+ def _assume_role_session(role_arn):
73
+ """Assume an IAM role and return a boto3 session with temporary credentials."""
74
+ sts = boto3.client("sts", region_name=_region)
75
+ creds = sts.assume_role(
76
+ RoleArn=role_arn,
77
+ RoleSessionName="agentcore-payment-plugin",
78
+ )["Credentials"]
79
+ return boto3.Session(
80
+ aws_access_key_id=creds["AccessKeyId"],
81
+ aws_secret_access_key=creds["SecretAccessKey"],
82
+ aws_session_token=creds["SessionToken"],
83
+ region_name=_region,
84
+ )
85
+
86
+
87
+ def create_payments_plugin(user_id, instrument_id=None, session_id=None):
88
+ """Create a fresh plugin instance per invocation.
89
+
90
+ Args:
91
+ user_id: From invocation context (required for SigV4, derived from JWT for bearer)
92
+ instrument_id: From invocation payload (created by app backend per user)
93
+ session_id: From invocation payload (created by app backend per conversation)
94
+
95
+ Returns:
96
+ AgentCorePaymentsPlugin instance, or None if no manager is configured.
97
+ """
98
+ if not _manager_arn:
99
+ return None
100
+
101
+ config_kwargs = {
102
+ "payment_manager_arn": _manager_arn,
103
+ "region": _region,
104
+ "payment_instrument_id": instrument_id,
105
+ "payment_session_id": session_id,
106
+ "payment_connector_id": _connector_id,
107
+ }
108
+
109
+ config_kwargs["auto_payment"] = _auto_payment
110
+ if _allowlist:
111
+ config_kwargs["payment_tool_allowlist"] = _allowlist
112
+ if _network_prefs:
113
+ config_kwargs["network_preferences_config"] = _network_prefs
114
+
115
+ if _process_payment_role_arn:
116
+ # Only pass boto3_session if SDK supports it (added in bedrock-agentcore >= 1.11).
117
+ # Older SDKs use the runtime role's default credentials and can still call ProcessPayment
118
+ # if the runtime role has been granted permission directly.
119
+ import inspect
120
+ if "boto3_session" in inspect.signature(AgentCorePaymentsPluginConfig).parameters:
121
+ config_kwargs["boto3_session"] = _assume_role_session(_process_payment_role_arn)
122
+ else:
123
+ logger.warning(
124
+ "PROCESS_PAYMENT_ROLE_ARN set but bedrock-agentcore SDK does not support boto3_session. "
125
+ "Upgrade to bedrock-agentcore>=1.11 to enable cross-role payment processing."
126
+ )
127
+
128
+ if _auth_mode == "bearer":
129
+ bearer_token = os.getenv("AGENTCORE_BEARER_TOKEN")
130
+ if bearer_token:
131
+ config_kwargs["bearer_token"] = bearer_token
132
+ else:
133
+ logger.warning(
134
+ "Bearer auth mode configured but AGENTCORE_BEARER_TOKEN not set. "
135
+ "Falling back to SigV4. Set AGENTCORE_BEARER_TOKEN or pass bearer_token in invoke context."
136
+ )
137
+ config_kwargs["user_id"] = user_id or "default-user"
138
+ else:
139
+ config_kwargs["user_id"] = user_id or "default-user"
140
+
141
+ config = AgentCorePaymentsPluginConfig(**config_kwargs)
142
+ return AgentCorePaymentsPlugin(config=config)
@@ -1,7 +1,65 @@
1
+ {{#if needsOs}}
2
+ import os
3
+ {{/if}}
1
4
  from mcp.server.fastmcp import FastMCP
2
5
 
3
6
  mcp = FastMCP("{{ name }}", host="0.0.0.0", stateless_http=True)
4
7
 
8
+ {{#if needsOs}}
9
+ _MOUNT_PATHS = [
10
+ {{#if sessionStorageMountPath}}"{{sessionStorageMountPath}}",{{/if}}
11
+ {{#each efsMounts}}"{{mountPath}}",{{/each}}
12
+ {{#each s3Mounts}}"{{mountPath}}",{{/each}}
13
+ ]
14
+
15
+ def _safe_resolve(path: str) -> str:
16
+ resolved = os.path.realpath(path)
17
+ if not any(resolved == os.path.realpath(m) or resolved.startswith(os.path.realpath(m) + os.sep) for m in _MOUNT_PATHS):
18
+ raise ValueError(f"Path '{path}' is not within any configured mount ({', '.join(_MOUNT_PATHS)})")
19
+ return resolved
20
+
21
+ @mcp.tool()
22
+ def file_read(path: str) -> str:
23
+ """Read a file from a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
24
+ try:
25
+ full_path = _safe_resolve(path)
26
+ with open(full_path) as f:
27
+ return f.read()
28
+ except ValueError as e:
29
+ return str(e)
30
+ except OSError as e:
31
+ return f"Error reading '{path}': {e.strerror}"
32
+
33
+ @mcp.tool()
34
+ def file_write(path: str, content: str) -> str:
35
+ """Write a file to a mounted filesystem. Use the absolute path (e.g. /mnt/tools/data.txt)."""
36
+ try:
37
+ full_path = _safe_resolve(path)
38
+ parent = os.path.dirname(full_path)
39
+ if parent:
40
+ os.makedirs(parent, exist_ok=True)
41
+ with open(full_path, "w") as f:
42
+ f.write(content)
43
+ return f"Written to {path}"
44
+ except ValueError as e:
45
+ return str(e)
46
+ except OSError as e:
47
+ return f"Error writing '{path}': {e.strerror}"
48
+
49
+ @mcp.tool()
50
+ def list_files(path: str) -> str:
51
+ """List files in a mounted filesystem directory. Use the absolute path (e.g. /mnt/tools)."""
52
+ try:
53
+ full_path = _safe_resolve(path)
54
+ entries = os.listdir(full_path)
55
+ return "\n".join(entries) if entries else "(empty directory)"
56
+ except ValueError as e:
57
+ return str(e)
58
+ except OSError as e:
59
+ return f"Error listing '{path}': {e.strerror}"
60
+
61
+ {{/if}}
62
+
5
63
 
6
64
  @mcp.tool()
7
65
  def add_numbers(a: int, b: int) -> int: