@aws/agentcore 1.0.0-preview.12 → 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 (48) hide show
  1. package/dist/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap +414 -14
  2. package/dist/assets/cdk/bin/cdk.ts +49 -5
  3. package/dist/assets/cdk/lib/cdk-stack.ts +162 -1
  4. package/dist/assets/cdk/test/cdk.test.ts +2 -0
  5. package/dist/assets/python/a2a/langchain_langgraph/base/pyproject.toml +1 -1
  6. package/dist/assets/python/agui/googleadk/base/pyproject.toml +1 -1
  7. package/dist/assets/python/agui/langchain_langgraph/base/pyproject.toml +1 -1
  8. package/dist/assets/python/http/autogen/base/pyproject.toml +1 -1
  9. package/dist/assets/python/http/langchain_langgraph/base/pyproject.toml +1 -1
  10. package/dist/assets/python/http/openaiagents/base/pyproject.toml +1 -1
  11. package/dist/assets/python/http/strands/base/main.py +39 -2
  12. package/dist/assets/python/http/strands/capabilities/payments/__init__.py +4 -0
  13. package/dist/assets/python/http/strands/capabilities/payments/payments.py +142 -0
  14. package/dist/cli/index.mjs +571 -505
  15. package/dist/lib/packaging/helpers.d.ts.map +1 -1
  16. package/dist/lib/packaging/helpers.js +50 -10
  17. package/dist/lib/packaging/helpers.js.map +1 -1
  18. package/dist/lib/utils/env.d.ts +4 -0
  19. package/dist/lib/utils/env.d.ts.map +1 -1
  20. package/dist/lib/utils/env.js +18 -0
  21. package/dist/lib/utils/env.js.map +1 -1
  22. package/dist/lib/utils/index.d.ts +1 -1
  23. package/dist/lib/utils/index.d.ts.map +1 -1
  24. package/dist/lib/utils/index.js +2 -1
  25. package/dist/lib/utils/index.js.map +1 -1
  26. package/dist/schema/schemas/agentcore-project.d.ts +57 -2
  27. package/dist/schema/schemas/agentcore-project.d.ts.map +1 -1
  28. package/dist/schema/schemas/agentcore-project.js +61 -5
  29. package/dist/schema/schemas/agentcore-project.js.map +1 -1
  30. package/dist/schema/schemas/deployed-state.d.ts +97 -0
  31. package/dist/schema/schemas/deployed-state.d.ts.map +1 -1
  32. package/dist/schema/schemas/deployed-state.js +24 -4
  33. package/dist/schema/schemas/deployed-state.js.map +1 -1
  34. package/dist/schema/schemas/primitives/harness.d.ts +33 -0
  35. package/dist/schema/schemas/primitives/harness.d.ts.map +1 -1
  36. package/dist/schema/schemas/primitives/harness.js +42 -1
  37. package/dist/schema/schemas/primitives/harness.js.map +1 -1
  38. package/dist/schema/schemas/primitives/index.d.ts +4 -2
  39. package/dist/schema/schemas/primitives/index.d.ts.map +1 -1
  40. package/dist/schema/schemas/primitives/index.js +14 -2
  41. package/dist/schema/schemas/primitives/index.js.map +1 -1
  42. package/dist/schema/schemas/primitives/payment.d.ts +54 -0
  43. package/dist/schema/schemas/primitives/payment.d.ts.map +1 -0
  44. package/dist/schema/schemas/primitives/payment.js +79 -0
  45. package/dist/schema/schemas/primitives/payment.js.map +1 -0
  46. package/npm-shrinkwrap.json +748 -342
  47. package/package.json +2 -1
  48. package/scripts/run-e2e-local.sh +9 -3
@@ -28,12 +28,14 @@ async function main() {
28
28
  const spec = await configIO.readProjectSpec();
29
29
  const targets = await configIO.readAWSDeploymentTargets();
30
30
 
31
- // Extract MCP configuration from project spec.
32
- // Gateway fields are stored in agentcore.json but may not yet be on the
33
- // AgentCoreProjectSpec type from @aws/agentcore-cdk, so we read them
34
- // dynamically and cast the resulting object.
31
+ // The vended CDK project compiles against the published @aws/agentcore-cdk
32
+ // schema type, which may lag the CLI's own AgentCoreProjectSpec (e.g. payments,
33
+ // harnesses, gateway fields). Cast once so those fields are reachable.
35
34
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
35
  const specAny = spec as any;
36
+
37
+ // Extract MCP configuration from project spec.
38
+ // Gateway fields are stored in agentcore.json but may not yet be on the
37
39
  const mcpSpec = specAny.agentCoreGateways?.length
38
40
  ? {
39
41
  agentCoreGateways: specAny.agentCoreGateways,
@@ -68,6 +70,7 @@ async function main() {
68
70
  apiKeyArn?: string;
69
71
  efsAccessPoints?: { accessPointArn: string; mountPath: string }[];
70
72
  s3AccessPoints?: { accessPointArn: string; mountPath: string }[];
73
+ apiFormat?: 'converse_stream' | 'responses' | 'chat_completions';
71
74
  }[] = [];
72
75
  for (const entry of specAny.harnesses ?? []) {
73
76
  const harnessDir = path.resolve(projectRoot, entry.path);
@@ -86,6 +89,7 @@ async function main() {
86
89
  apiKeyArn: harnessSpec.model?.apiKeyArn,
87
90
  efsAccessPoints: harnessSpec.efsAccessPoints,
88
91
  s3AccessPoints: harnessSpec.s3AccessPoints,
92
+ apiFormat: harnessSpec.model?.apiFormat,
89
93
  });
90
94
  } catch (err) {
91
95
  throw new Error(
@@ -109,11 +113,51 @@ async function main() {
109
113
  | Record<string, { credentialProviderArn: string; clientSecretArn?: string }>
110
114
  | undefined;
111
115
 
116
+ // Payment credential provider ARNs live in the same credentials map as identity credentials
117
+ const paymentCredentials = credentials;
118
+
119
+ const paymentSpec = specAny.payments?.length
120
+ ? specAny.payments.map(
121
+ (p: {
122
+ name: string;
123
+ description?: string;
124
+ authorizerType: 'AWS_IAM' | 'CUSTOM_JWT';
125
+ authorizerConfiguration?: unknown;
126
+ autoPayment?: boolean;
127
+ paymentToolAllowlist?: string[];
128
+ networkPreferences?: string[];
129
+ connectors: { name: string; provider?: string; credentialName: string }[];
130
+ }) => ({
131
+ name: p.name,
132
+ description: p.description,
133
+ authorizerType: p.authorizerType,
134
+ authorizerConfiguration: p.authorizerConfiguration,
135
+ autoPayment: p.autoPayment,
136
+ paymentToolAllowlist: p.paymentToolAllowlist,
137
+ networkPreferences: p.networkPreferences,
138
+ connectors: p.connectors.map(c => {
139
+ const credentialProviderArn = paymentCredentials?.[c.credentialName]?.credentialProviderArn;
140
+ if (!credentialProviderArn) {
141
+ // Fail fast with an actionable message rather than passing an empty
142
+ // ARN that fails opaquely server-side at CreatePaymentConnector.
143
+ throw new Error(
144
+ `Payment connector "${c.name}" on manager "${p.name}" references credential ` +
145
+ `"${c.credentialName}", but no deployed credential provider was found for it. ` +
146
+ `Run \`agentcore deploy\` so the credential provider is created first.`
147
+ );
148
+ }
149
+ return { name: c.name, provider: c.provider, credentialProviderArn };
150
+ }),
151
+ })
152
+ )
153
+ : undefined;
154
+
112
155
  new AgentCoreStack(app, stackName, {
113
156
  spec,
114
157
  mcpSpec,
115
158
  credentials,
116
159
  harnesses: harnessConfigs.length > 0 ? harnessConfigs : undefined,
160
+ paymentSpec,
117
161
  env,
118
162
  description: `AgentCore stack for ${spec.name} deployed to ${target.name} (${target.region})`,
119
163
  tags: {
@@ -128,5 +172,5 @@ async function main() {
128
172
 
129
173
  main().catch((error: unknown) => {
130
174
  console.error('AgentCore CDK synthesis failed:', error instanceof Error ? error.message : error);
131
- process.exitCode = 1;
175
+ process.exit(1);
132
176
  });
@@ -1,10 +1,14 @@
1
1
  import {
2
2
  AgentCoreApplication,
3
3
  AgentCoreMcp,
4
+ AgentCorePaymentManager,
5
+ AgentCorePaymentConnector,
4
6
  type AgentCoreProjectSpec,
5
7
  type AgentCoreMcpSpec,
8
+ type CustomJWTAuthorizerConfig,
6
9
  } from '@aws/agentcore-cdk';
7
10
  import { CfnOutput, Stack, type StackProps } from 'aws-cdk-lib';
11
+ import * as iam from 'aws-cdk-lib/aws-iam';
8
12
  import { Construct } from 'constructs';
9
13
 
10
14
  export interface HarnessConfig {
@@ -21,6 +25,23 @@ export interface HarnessConfig {
21
25
  s3AccessPoints?: { accessPointArn: string; mountPath: string }[];
22
26
  }
23
27
 
28
+ export interface PaymentConnectorSpec {
29
+ name: string;
30
+ provider: 'CoinbaseCDP' | 'StripePrivy';
31
+ credentialProviderArn: string;
32
+ }
33
+
34
+ export interface PaymentSpec {
35
+ name: string;
36
+ description?: string;
37
+ authorizerType: 'AWS_IAM' | 'CUSTOM_JWT';
38
+ authorizerConfiguration?: { customJWTAuthorizer: CustomJWTAuthorizerConfig };
39
+ autoPayment?: boolean;
40
+ paymentToolAllowlist?: string[];
41
+ networkPreferences?: string[];
42
+ connectors: PaymentConnectorSpec[];
43
+ }
44
+
24
45
  export interface AgentCoreStackProps extends StackProps {
25
46
  /**
26
47
  * The AgentCore project specification containing agents, memories, and credentials.
@@ -38,6 +59,30 @@ export interface AgentCoreStackProps extends StackProps {
38
59
  * Harness role configurations.
39
60
  */
40
61
  harnesses?: HarnessConfig[];
62
+ /**
63
+ * Payment specifications with resolved credential provider ARNs.
64
+ */
65
+ paymentSpec?: PaymentSpec[];
66
+ }
67
+
68
+ function toCdkId(name: string): string {
69
+ return name.replace(/_/g, '');
70
+ }
71
+
72
+ /**
73
+ * Decide whether a deployed runtime should receive payment env vars + IAM grants.
74
+ * Payments today only ships a runtime shim for Python HTTP runtimes; injecting
75
+ * AGENTCORE_PAYMENT_* env vars into TypeScript / MCP / A2A / AGUI runtimes
76
+ * would surface env vars they cannot consume and would dilute least-privilege
77
+ * IAM grants for runtimes that never call ProcessPayment.
78
+ */
79
+ function isPaymentEligibleAgent(agent: { entrypoint?: string; protocol?: string }): boolean {
80
+ if (agent.protocol && agent.protocol !== 'HTTP') {
81
+ return false;
82
+ }
83
+ const entrypoint = typeof agent.entrypoint === 'string' ? agent.entrypoint : '';
84
+ const entrypointFile = entrypoint.split(':')[0] ?? '';
85
+ return entrypointFile.endsWith('.py');
41
86
  }
42
87
 
43
88
  /**
@@ -53,7 +98,7 @@ export class AgentCoreStack extends Stack {
53
98
  constructor(scope: Construct, id: string, props: AgentCoreStackProps) {
54
99
  super(scope, id, props);
55
100
 
56
- const { spec, mcpSpec, credentials, harnesses } = props;
101
+ const { spec, mcpSpec, credentials, harnesses, paymentSpec } = props;
57
102
 
58
103
  // Create AgentCoreApplication with all agents and harness roles
59
104
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -74,6 +119,122 @@ export class AgentCoreStack extends Stack {
74
119
  });
75
120
  }
76
121
 
122
+ // Create payment infrastructure via CFN constructs
123
+ if (paymentSpec && paymentSpec.length > 0) {
124
+ for (const payment of paymentSpec) {
125
+ const mgrId = toCdkId(payment.name);
126
+ const manager = new AgentCorePaymentManager(this, `Payment${mgrId}`, {
127
+ projectName: spec.name,
128
+ name: payment.name,
129
+ authorizerType: payment.authorizerType,
130
+ description: payment.description,
131
+ authorizerConfiguration: payment.authorizerConfiguration,
132
+ tags: spec.tags,
133
+ });
134
+
135
+ const prefix = `AGENTCORE_PAYMENT_${payment.name.toUpperCase().replace(/-/g, '_')}`;
136
+
137
+ // Wire env vars from construct output tokens into eligible agent environments only.
138
+ // See isPaymentEligibleAgent — non-Python or non-HTTP runtimes have no shim that
139
+ // can consume these env vars, and giving them sts:AssumeRole on the
140
+ // ProcessPaymentRole would broaden the privilege surface unnecessarily.
141
+ for (const env of this.application.environments.values()) {
142
+ if (!isPaymentEligibleAgent(env.agent)) {
143
+ continue;
144
+ }
145
+ env.runtime.addEnvironmentVariable(`${prefix}_MANAGER_ARN`, manager.paymentManagerArn);
146
+ env.runtime.addEnvironmentVariable(`${prefix}_PROCESS_PAYMENT_ROLE_ARN`, manager.processPaymentRoleArn);
147
+
148
+ // Grant runtime execution role permission to assume the ProcessPaymentRole.
149
+ // The ProcessPaymentRole's trust policy allows AccountRootPrincipal, but the
150
+ // caller still needs sts:AssumeRole on its own role to perform the assumption.
151
+ env.runtime.role.addToPrincipalPolicy(
152
+ new iam.PolicyStatement({
153
+ actions: ['sts:AssumeRole'],
154
+ resources: [manager.processPaymentRoleArn],
155
+ })
156
+ );
157
+
158
+ // Grant payment data-plane actions directly to the runtime role.
159
+ //
160
+ // NOTE: This deviates from the canonical role model in the AgentCore Payments
161
+ // beta guide, which assigns Get/List/Create instrument+session actions to a
162
+ // separate ManagementRole and limits the agent's role to ProcessPayment only.
163
+ // The current SDK plugin (AgentCorePaymentsPlugin.generate_payment_header)
164
+ // calls GetPaymentInstrument internally during the 402 auto-pay path, so the
165
+ // runtime role needs read access. CreatePaymentSession is included so
166
+ // `agentcore invoke --auto-session` works without a separate ManagementRole
167
+ // call. Tighten this if the SDK is updated to accept pre-fetched instrument
168
+ // details and split create-session into a backend-only flow.
169
+ env.runtime.role.addToPrincipalPolicy(
170
+ new iam.PolicyStatement({
171
+ actions: [
172
+ 'bedrock-agentcore:GetPaymentInstrument',
173
+ 'bedrock-agentcore:ListPaymentInstruments',
174
+ 'bedrock-agentcore:GetPaymentInstrumentBalance',
175
+ 'bedrock-agentcore:GetPaymentSession',
176
+ 'bedrock-agentcore:ListPaymentSessions',
177
+ 'bedrock-agentcore:CreatePaymentSession',
178
+ 'bedrock-agentcore:ProcessPayment',
179
+ ],
180
+ resources: [manager.paymentManagerArn, `${manager.paymentManagerArn}/*`],
181
+ })
182
+ );
183
+
184
+ if (payment.autoPayment !== undefined) {
185
+ env.runtime.addEnvironmentVariable(`${prefix}_AUTO_PAYMENT`, String(payment.autoPayment));
186
+ }
187
+ if (payment.paymentToolAllowlist) {
188
+ env.runtime.addEnvironmentVariable(`${prefix}_TOOL_ALLOWLIST`, payment.paymentToolAllowlist.join(','));
189
+ }
190
+ if (payment.networkPreferences) {
191
+ env.runtime.addEnvironmentVariable(`${prefix}_NETWORK_PREFERENCES`, payment.networkPreferences.join(','));
192
+ }
193
+ if (payment.authorizerType === 'CUSTOM_JWT') {
194
+ env.runtime.addEnvironmentVariable(`${prefix}_AUTH_MODE`, 'bearer');
195
+ }
196
+ }
197
+
198
+ // Create connectors for this manager
199
+ for (const connector of payment.connectors) {
200
+ const connId = toCdkId(connector.name);
201
+ const conn = new AgentCorePaymentConnector(this, `Payment${mgrId}${connId}`, {
202
+ projectName: spec.name,
203
+ paymentManager: manager,
204
+ connectorName: connector.name,
205
+ connectorType: connector.provider,
206
+ credentialProviderArn: connector.credentialProviderArn,
207
+ });
208
+
209
+ // Wire first connector's ID as env var (eligible agents only)
210
+ if (connector === payment.connectors[0]) {
211
+ for (const env of this.application.environments.values()) {
212
+ if (!isPaymentEligibleAgent(env.agent)) continue;
213
+ env.runtime.addEnvironmentVariable(`${prefix}_CONNECTOR_ID`, conn.paymentConnectorId);
214
+ }
215
+ }
216
+
217
+ new CfnOutput(this, `Payment${mgrId}${connId}ConnectorId`, {
218
+ value: conn.paymentConnectorId,
219
+ });
220
+ }
221
+
222
+ // CFN Outputs for post-deploy state parsing
223
+ new CfnOutput(this, `Payment${mgrId}ManagerArn`, {
224
+ value: manager.paymentManagerArn,
225
+ });
226
+ new CfnOutput(this, `Payment${mgrId}ManagerId`, {
227
+ value: manager.paymentManagerId,
228
+ });
229
+ new CfnOutput(this, `Payment${mgrId}ProcessPaymentRoleArn`, {
230
+ value: manager.processPaymentRoleArn,
231
+ });
232
+ new CfnOutput(this, `Payment${mgrId}ResourceRetrievalRoleArn`, {
233
+ value: manager.resourceRetrievalRoleArn,
234
+ });
235
+ }
236
+ }
237
+
77
238
  // Stack-level output
78
239
  new CfnOutput(this, 'StackNameOutput', {
79
240
  description: 'Name of the CloudFormation Stack',
@@ -15,6 +15,8 @@ test('AgentCoreStack synthesizes with empty spec', () => {
15
15
  evaluators: [],
16
16
  onlineEvalConfigs: [],
17
17
  policyEngines: [],
18
+ payments: [],
19
+ configBundles: [],
18
20
  agentCoreGateways: [],
19
21
  mcpRuntimeTools: [],
20
22
  unassignedTargets: [],
@@ -17,7 +17,7 @@ dependencies = [
17
17
  {{/if}}{{#if (eq modelProvider "OpenAI")}}"langchain-openai >= 0.2.0",
18
18
  {{/if}}"aws-opentelemetry-distro",
19
19
  "opentelemetry-instrumentation-langchain >= 0.59.0",
20
- "bedrock-agentcore[a2a] >= 1.0.3",
20
+ "bedrock-agentcore[a2a] >= 1.8.0",
21
21
  "botocore[crt] >= 1.35.0",
22
22
  "langgraph >= 0.2.0",
23
23
  ]
@@ -11,7 +11,7 @@ requires-python = ">=3.10"
11
11
  dependencies = [
12
12
  "ag-ui-adk >= 0.6.0",
13
13
  "ag-ui-protocol >= 0.1.10",
14
- "bedrock-agentcore >= 1.0.3",
14
+ "bedrock-agentcore >= 1.8.0",
15
15
  "fastapi >= 0.115.12",
16
16
  "google-adk >= 1.16.0, < 2.0.0",
17
17
  "google-genai >= 1.0.0, < 2.0.0",
@@ -18,7 +18,7 @@ dependencies = [
18
18
  {{/if}}{{#if (eq modelProvider "OpenAI")}}"langchain-openai >= 0.2.0",
19
19
  {{/if}}"aws-opentelemetry-distro",
20
20
  "opentelemetry-instrumentation-langchain >= 0.59.0",
21
- "bedrock-agentcore >= 1.0.3",
21
+ "bedrock-agentcore >= 1.8.0",
22
22
  "botocore[crt] >= 1.35.0",
23
23
  "langgraph >= 0.3.25",
24
24
  "langchain >= 0.3.0",
@@ -13,7 +13,7 @@ dependencies = [
13
13
  "autogen-ext[mcp] >= 0.7.5",
14
14
  "opentelemetry-distro",
15
15
  "opentelemetry-exporter-otlp",
16
- "bedrock-agentcore >= 1.0.3",
16
+ "bedrock-agentcore >= 1.8.0",
17
17
  "botocore[crt] >= 1.35.0",
18
18
  "tiktoken",
19
19
  {{#if (eq modelProvider "Bedrock")}}
@@ -15,7 +15,7 @@ dependencies = [
15
15
  "mcp >= 1.19.0",
16
16
  "langchain-mcp-adapters >= 0.1.11",
17
17
  "langchain >= 1.0.3",
18
- "bedrock-agentcore >= 1.0.3",
18
+ "bedrock-agentcore >= 1.8.0",
19
19
  "botocore[crt] >= 1.35.0",
20
20
  {{#if (eq modelProvider "Bedrock")}}
21
21
  "langchain-aws >= 1.0.0",
@@ -11,7 +11,7 @@ requires-python = ">=3.10"
11
11
  dependencies = [
12
12
  "aws-opentelemetry-distro",
13
13
  "openai-agents >= 0.4.2",
14
- "bedrock-agentcore >= 1.0.3",
14
+ "bedrock-agentcore >= 1.8.0",
15
15
  "botocore[crt] >= 1.35.0",
16
16
  {{#if hasGateway}}{{#if (includes gatewayAuthTypes "AWS_IAM")}}"mcp-proxy-for-aws >= 1.1.0",
17
17
  {{/if}}{{/if}}
@@ -18,6 +18,9 @@ from memory.session import get_memory_session_manager
18
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
@@ -153,12 +156,12 @@ class ConfigBundleHook(HookProvider):
153
156
  {{/if}}
154
157
 
155
158
  {{#if hasMemory}}
159
+ {{#unless hasPayment}}
156
160
  def agent_factory():
157
161
  cache = {}
158
162
  def get_or_create_agent(session_id, user_id):
159
163
  key = f"{session_id}/{user_id}"
160
164
  if key not in cache:
161
- # Create an agent for the given session_id and user_id
162
165
  cache[key] = Agent(
163
166
  model=load_model(),
164
167
  session_manager=get_memory_session_manager(session_id, user_id),
@@ -169,6 +172,7 @@ def agent_factory():
169
172
  return cache[key]
170
173
  return get_or_create_agent
171
174
  get_or_create_agent = agent_factory()
175
+ {{/unless}}
172
176
  {{else}}
173
177
  {{#if hasConfigBundle}}
174
178
  def create_agent():
@@ -179,6 +183,7 @@ def create_agent():
179
183
  hooks=[ConfigBundleHook()],
180
184
  )
181
185
  {{else}}
186
+ {{#unless hasPayment}}
182
187
  _agent = None
183
188
 
184
189
  def get_or_create_agent():
@@ -187,9 +192,10 @@ def get_or_create_agent():
187
192
  _agent = Agent(
188
193
  model=load_model(),
189
194
  system_prompt=DEFAULT_SYSTEM_PROMPT,
190
- tools=tools
195
+ tools=tools,
191
196
  )
192
197
  return _agent
198
+ {{/unless}}
193
199
  {{/if}}
194
200
  {{/if}}
195
201
 
@@ -198,16 +204,47 @@ def get_or_create_agent():
198
204
  async def invoke(payload, context):
199
205
  log.info("Invoking Agent.....")
200
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
+
201
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}}
202
228
  session_id = getattr(context, 'session_id', 'default-session')
203
229
  user_id = getattr(context, 'user_id', 'default-user')
204
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
+ )
205
241
  {{else}}
206
242
  {{#if hasConfigBundle}}
207
243
  agent = create_agent()
208
244
  {{else}}
209
245
  agent = get_or_create_agent()
210
246
  {{/if}}
247
+ {{/if}}
211
248
  {{/if}}
212
249
 
213
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)