Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
dc15b38
feat(project): model payment connector variants
aidandaly24 Aug 26, 2026
752c1df
feat(project): support payment resource mutations
aidandaly24 Aug 26, 2026
d1858b2
feat(project): add payment credential command
aidandaly24 Aug 26, 2026
3f153fc
feat(project): add payment manager and connector commands
aidandaly24 Aug 26, 2026
f232a03
feat(project): wire Quick Create into generated CDK
aidandaly24 Aug 26, 2026
49b8139
refactor(project): use type aliases for payment connector specs
aidandaly24 Aug 26, 2026
9cdfd58
refactor(project): clarify payment connector construction
aidandaly24 Aug 26, 2026
521dd63
feat(project): add payment manager removal
aidandaly24 Aug 26, 2026
cdd0910
feat(project): add payment connector removal
aidandaly24 Aug 27, 2026
580bed9
fix(project): reject payment manager environment collisions
aidandaly24 Aug 27, 2026
51d9ba9
fix(project): guard quick create for legacy CDK assets
aidandaly24 Aug 27, 2026
ab65a63
fix(project): preserve payment connector CDK identities
aidandaly24 Aug 27, 2026
f7fa5c5
fix(project): collect payment credential values
aidandaly24 Aug 27, 2026
8be1082
fix(project): persist connector-created payment credentials
aidandaly24 Aug 27, 2026
0fc5ba3
fix(project): require explicit payment credentials
aidandaly24 Aug 27, 2026
1f5888f
style(project): format payment connector validation
aidandaly24 Aug 27, 2026
519eb6b
refactor(project): centralize credential environment keys
aidandaly24 Aug 27, 2026
7e1468b
fix(project): reject credential environment key collisions
aidandaly24 Aug 27, 2026
4f95223
fix(project): separate payment CDK identity namespaces
aidandaly24 Aug 27, 2026
f142215
fix(project): remove generated CDK capability marker
aidandaly24 Aug 27, 2026
d1948b8
fix(project): validate payment credential keys
aidandaly24 Aug 27, 2026
5f0e064
fix(project): secure local credential storage
aidandaly24 Aug 27, 2026
1012747
fix(project): align payment manager validation
aidandaly24 Aug 27, 2026
85e9013
fix(project): warn about payment runtime behavior
aidandaly24 Aug 27, 2026
9564640
fix(project): match Coinbase key service format
aidandaly24 Aug 27, 2026
458ef85
fix(project): avoid unenforced spend cap warning
aidandaly24 Aug 28, 2026
d6246d6
fix(project): harden existing secret file permissions
aidandaly24 Aug 28, 2026
63c3f94
fix(project): enforce payment service input constraints
aidandaly24 Aug 28, 2026
d63ca1c
refactor(project): delegate payments to L3 construct
aidandaly24 Aug 27, 2026
009bb71
refactor(project): share payment spend-limit validation
aidandaly24 Aug 28, 2026
7adecd9
fix(project): accept legacy blank spend limits
aidandaly24 Aug 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 2 additions & 44 deletions src/assets/cdk/bin/cdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,61 +146,19 @@ async function main() {

// Extract credentials from deployed state for this target
const targetState = (deployedState as Record<string, unknown>)?.targets as
| Record<string, Record<string, unknown>>
| undefined;
Record<string, Record<string, unknown>> | undefined;
const targetResources = target
? (targetState?.[target.name]?.resources as Record<string, unknown> | undefined)
: undefined;
const credentials = targetResources?.credentials as
| Record<string, { credentialProviderArn: string; clientSecretArn?: string }>
| undefined;

// Payment credential provider ARNs live in the same credentials map as identity credentials
const paymentCredentials = credentials;

const paymentSpec = specAny.payments?.length
? specAny.payments.map(
(p: {
name: string;
description?: string;
authorizerType: 'AWS_IAM' | 'CUSTOM_JWT';
authorizerConfiguration?: unknown;
autoPayment?: boolean;
paymentToolAllowlist?: string[];
networkPreferences?: string[];
connectors: { name: string; provider?: string; credentialName: string }[];
}) => ({
name: p.name,
description: p.description,
authorizerType: p.authorizerType,
authorizerConfiguration: p.authorizerConfiguration,
autoPayment: p.autoPayment,
paymentToolAllowlist: p.paymentToolAllowlist,
networkPreferences: p.networkPreferences,
connectors: p.connectors.map(c => {
const credentialProviderArn = paymentCredentials?.[c.credentialName]?.credentialProviderArn;
if (!credentialProviderArn) {
// Fail fast with an actionable message rather than passing an empty
// ARN that fails opaquely server-side at CreatePaymentConnector.
throw new Error(
`Payment connector "${c.name}" on manager "${p.name}" references credential ` +
`"${c.credentialName}", but no deployed credential provider was found for it. ` +
`Run \`agentcore deploy\` so the credential provider is created first.`
);
}
return { name: c.name, provider: c.provider, credentialProviderArn };
}),
})
)
: undefined;
Record<string, { credentialProviderArn: string; clientSecretArn?: string }> | undefined;

new AgentCoreStack(app, stackName, {
spec,
mcpSpec,
credentials,
connectorParametersByFile,
harnesses: harnessConfigs.length > 0 ? harnessConfigs : undefined,
paymentSpec,
env,
description: target
? `AgentCore stack for ${spec.name} deployed to ${target.name} (${target.region})`
Expand Down
169 changes: 7 additions & 162 deletions src/assets/cdk/lib/cdk-stack.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import {
AgentCoreApplication,
AgentCoreMcp,
AgentCorePaymentManager,
AgentCorePaymentConnector,
AgentCorePayments,
type AgentCoreProjectSpec,
type AgentCoreMcpSpec,
type CustomJWTAuthorizerConfig,
type HarnessDeploymentConfig,
} from '@aws/agentcore-cdk';
import { CfnOutput, Stack, type StackProps } from 'aws-cdk-lib';
import * as iam from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';

/**
Expand All @@ -19,23 +16,6 @@ import { Construct } from 'constructs';
*/
export type HarnessConfig = HarnessDeploymentConfig;

export interface PaymentConnectorSpec {
name: string;
provider: 'CoinbaseCDP' | 'StripePrivy';
credentialProviderArn: string;
}

export interface PaymentSpec {
name: string;
description?: string;
authorizerType: 'AWS_IAM' | 'CUSTOM_JWT';
authorizerConfiguration?: { customJWTAuthorizer: CustomJWTAuthorizerConfig };
autoPayment?: boolean;
paymentToolAllowlist?: string[];
networkPreferences?: string[];
connectors: PaymentConnectorSpec[];
}

export interface AgentCoreStackProps extends StackProps {
/**
* The AgentCore project specification containing agents, memories, and credentials.
Expand All @@ -58,30 +38,6 @@ export interface AgentCoreStackProps extends StackProps {
* connectorConfigFile path. Forwarded to AgentCoreApplication.
*/
connectorParametersByFile?: Record<string, Record<string, unknown>>;
/**
* Payment specifications with resolved credential provider ARNs.
*/
paymentSpec?: PaymentSpec[];
}

function toCdkId(name: string): string {
return name.replace(/_/g, '');
}

/**
* Decide whether a deployed runtime should receive payment env vars + IAM grants.
* Payments today only ships a runtime shim for Python HTTP runtimes; injecting
* AGENTCORE_PAYMENT_* env vars into TypeScript / MCP / A2A / AGUI runtimes
* would surface env vars they cannot consume and would dilute least-privilege
* IAM grants for runtimes that never call ProcessPayment.
*/
function isPaymentEligibleAgent(agent: { entrypoint?: string; protocol?: string }): boolean {
if (agent.protocol && agent.protocol !== 'HTTP') {
return false;
}
const entrypoint = typeof agent.entrypoint === 'string' ? agent.entrypoint : '';
const entrypointFile = entrypoint.split(':')[0] ?? '';
return entrypointFile.endsWith('.py');
}

/**
Expand All @@ -97,7 +53,7 @@ export class AgentCoreStack extends Stack {
constructor(scope: Construct, id: string, props: AgentCoreStackProps) {
super(scope, id, props);

const { spec, mcpSpec, credentials, harnesses, connectorParametersByFile, paymentSpec } = props;
const { spec, mcpSpec, credentials, harnesses, connectorParametersByFile } = props;

// Create AgentCoreApplication with all agents and harness roles
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand All @@ -112,6 +68,11 @@ export class AgentCoreStack extends Stack {
appProps.credentials = credentials;
}
this.application = new AgentCoreApplication(this, 'Application', appProps as any);
new AgentCorePayments(this, 'Payments', {
spec,
credentials,
agentCoreApplication: this.application,
});

// Create AgentCoreMcp if there are gateways configured
if (mcpSpec?.agentCoreGateways && mcpSpec.agentCoreGateways.length > 0) {
Expand All @@ -124,122 +85,6 @@ export class AgentCoreStack extends Stack {
});
}

// Create payment infrastructure via CFN constructs
if (paymentSpec && paymentSpec.length > 0) {
for (const payment of paymentSpec) {
const mgrId = toCdkId(payment.name);
const manager = new AgentCorePaymentManager(this, `Payment${mgrId}`, {
projectName: spec.name,
name: payment.name,
authorizerType: payment.authorizerType,
description: payment.description,
authorizerConfiguration: payment.authorizerConfiguration,
tags: spec.tags,
});

const prefix = `AGENTCORE_PAYMENT_${payment.name.toUpperCase().replace(/-/g, '_')}`;

// Wire env vars from construct output tokens into eligible agent environments only.
// See isPaymentEligibleAgent — non-Python or non-HTTP runtimes have no shim that
// can consume these env vars, and giving them sts:AssumeRole on the
// ProcessPaymentRole would broaden the privilege surface unnecessarily.
for (const env of this.application.environments.values()) {
if (!isPaymentEligibleAgent(env.agent)) {
continue;
}
env.runtime.addEnvironmentVariable(`${prefix}_MANAGER_ARN`, manager.paymentManagerArn);
env.runtime.addEnvironmentVariable(`${prefix}_PROCESS_PAYMENT_ROLE_ARN`, manager.processPaymentRoleArn);

// Grant runtime execution role permission to assume the ProcessPaymentRole.
// The ProcessPaymentRole's trust policy allows AccountRootPrincipal, but the
// caller still needs sts:AssumeRole on its own role to perform the assumption.
env.runtime.role.addToPrincipalPolicy(
new iam.PolicyStatement({
actions: ['sts:AssumeRole'],
resources: [manager.processPaymentRoleArn],
})
);

// Grant payment data-plane actions directly to the runtime role.
//
// NOTE: This deviates from the canonical role model in the AgentCore Payments
// beta guide, which assigns Get/List/Create instrument+session actions to a
// separate ManagementRole and limits the agent's role to ProcessPayment only.
// The current SDK plugin (AgentCorePaymentsPlugin.generate_payment_header)
// calls GetPaymentInstrument internally during the 402 auto-pay path, so the
// runtime role needs read access. CreatePaymentSession is included so
// `agentcore invoke --auto-session` works without a separate ManagementRole
// call. Tighten this if the SDK is updated to accept pre-fetched instrument
// details and split create-session into a backend-only flow.
env.runtime.role.addToPrincipalPolicy(
new iam.PolicyStatement({
actions: [
'bedrock-agentcore:GetPaymentInstrument',
'bedrock-agentcore:ListPaymentInstruments',
'bedrock-agentcore:GetPaymentInstrumentBalance',
'bedrock-agentcore:GetPaymentSession',
'bedrock-agentcore:ListPaymentSessions',
'bedrock-agentcore:CreatePaymentSession',
'bedrock-agentcore:ProcessPayment',
],
resources: [manager.paymentManagerArn, `${manager.paymentManagerArn}/*`],
})
);

if (payment.autoPayment !== undefined) {
env.runtime.addEnvironmentVariable(`${prefix}_AUTO_PAYMENT`, String(payment.autoPayment));
}
if (payment.paymentToolAllowlist) {
env.runtime.addEnvironmentVariable(`${prefix}_TOOL_ALLOWLIST`, payment.paymentToolAllowlist.join(','));
}
if (payment.networkPreferences) {
env.runtime.addEnvironmentVariable(`${prefix}_NETWORK_PREFERENCES`, payment.networkPreferences.join(','));
}
if (payment.authorizerType === 'CUSTOM_JWT') {
env.runtime.addEnvironmentVariable(`${prefix}_AUTH_MODE`, 'bearer');
}
}

// Create connectors for this manager
for (const connector of payment.connectors) {
const connId = toCdkId(connector.name);
const conn = new AgentCorePaymentConnector(this, `Payment${mgrId}${connId}`, {
projectName: spec.name,
paymentManager: manager,
connectorName: connector.name,
connectorType: connector.provider,
credentialProviderArn: connector.credentialProviderArn,
});

// Wire first connector's ID as env var (eligible agents only)
if (connector === payment.connectors[0]) {
for (const env of this.application.environments.values()) {
if (!isPaymentEligibleAgent(env.agent)) continue;
env.runtime.addEnvironmentVariable(`${prefix}_CONNECTOR_ID`, conn.paymentConnectorId);
}
}

new CfnOutput(this, `Payment${mgrId}${connId}ConnectorId`, {
value: conn.paymentConnectorId,
});
}

// CFN Outputs for post-deploy state parsing
new CfnOutput(this, `Payment${mgrId}ManagerArn`, {
value: manager.paymentManagerArn,
});
new CfnOutput(this, `Payment${mgrId}ManagerId`, {
value: manager.paymentManagerId,
});
new CfnOutput(this, `Payment${mgrId}ProcessPaymentRoleArn`, {
value: manager.processPaymentRoleArn,
});
new CfnOutput(this, `Payment${mgrId}ResourceRetrievalRoleArn`, {
value: manager.resourceRetrievalRoleArn,
});
}
}

// Stack-level output
new CfnOutput(this, 'StackNameOutput', {
description: 'Name of the CloudFormation Stack',
Expand Down
2 changes: 1 addition & 1 deletion src/assets/cdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"typescript": "~5.9.3"
},
"dependencies": {
"@aws/agentcore-cdk": "0.1.0-alpha.45",
"@aws/agentcore-cdk": "0.1.0-alpha.49",
"aws-cdk-lib": "~2.266.0",
"constructs": "~10.7.0"
}
Expand Down
Loading
Loading