Skip to content

feat(payments): add AgentCore Payments as first-class CLI resource#1261

Open
aidandaly24 wants to merge 34 commits into
mainfrom
feat/payments
Open

feat(payments): add AgentCore Payments as first-class CLI resource#1261
aidandaly24 wants to merge 34 commits into
mainfrom
feat/payments

Conversation

@aidandaly24
Copy link
Copy Markdown
Contributor

@aidandaly24 aidandaly24 commented May 14, 2026

Description

Adds AgentCore Payments as a first-class resource type in the CLI. Includes:

  • agentcore add payment-manager / payment-connector commands (CLI + TUI wizard)
  • agentcore remove payment-manager / payment-connector with cascading delete
  • CDK-backed deployment via AgentCorePaymentManager + AgentCorePaymentConnector L3 constructs
  • Payment credential provider setup (imperative, same /identities/ endpoint as API key/OAuth)
  • CFN output parsing for deployed-state persistence
  • Invoke flags: --payment-instrument-id, --payment-session-id, --auto-session
  • Python agent template with x402 payment interception via SDK plugin
  • Validate command checks for payment credential completeness
  • Dev mode env var injection (AUTH_MODE for CUSTOM_JWT support)

Related Issue

Closes #

Documentation PR

Type of Change

  • New feature

Testing

How have you tested the change?

  • I ran npm run test:unit and npm run test:integ
  • I ran npm run typecheck
  • I ran npm run lint
  • If I modified src/assets/, I ran npm run test:update-snapshots and committed the updated snapshots

120 new payment-specific unit tests added covering:

  • parsePaymentOutputs, PaymentManagerPrimitive, PaymentConnectorPrimitive
  • validate command payment paths, payment-env dev mode, pre-deploy credential setup
  • wirePaymentCapability template patching

Checklist

  • I have read the CONTRIBUTING document
  • I have added any necessary tests that prove my fix is effective or my feature works
  • I have updated the documentation accordingly
  • I have added an appropriate example to the documentation to outline the feature, or no new docs are needed
  • My changes generate no new warnings
  • Any dependent changes have been merged and published

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the
terms of your choice.


Update — runtime IAM + system prompt for x402 auto-pay (commit 1f6a2f2)

End-to-end testing surfaced a missing IAM grant and a stale system prompt. Both blockers are fixed in this commit; the rest of the PR is unchanged.

What broke without the fix

The agent runtime role only had sts:AssumeRole on the ProcessPaymentRole (which carries only ProcessPayment). But AgentCorePaymentsPlugin.generate_payment_header calls GetPaymentInstrument on the runtime's own credentials before any role assumption. The plugin failed with AccessDeniedException on the very first 402 it tried to settle:

Failed to get payment instrument: ... is not authorized to perform:
bedrock-agentcore:GetPaymentInstrument on resource: <pmgr-arn>
because no identity-based policy allows the bedrock-agentcore:GetPaymentInstrument action

Fix

Grant the runtime role the seven payment data-plane actions, scoped to the manager ARN, in the vended CDK stack template:

  • bedrock-agentcore:GetPaymentInstrument
  • bedrock-agentcore:ListPaymentInstruments
  • bedrock-agentcore:GetPaymentInstrumentBalance
  • bedrock-agentcore:GetPaymentSession
  • bedrock-agentcore:ListPaymentSessions
  • bedrock-agentcore:CreatePaymentSession (so --auto-session works without a separate ManagementRole call)
  • bedrock-agentcore:ProcessPayment

A code comment notes this deviates from the canonical 4-role split in the AgentCore Payments beta guide. The deviation is required by the current SDK plugin, which calls GetPaymentInstrument from inside generate_payment_header. If the SDK is later updated to accept a pre-fetched instrument and to split create-session into a backend-only flow, this grant can be tightened.

System-prompt update

PAYMENT_SYSTEM_PROMPT in capabilities/payments/payments.py now mentions the http_request tool that the SDK plugin auto-registers:

- Use http_request to call HTTP endpoints. 402 Payment Required responses
  are settled automatically by the plugin and the call is retried.

The other prompt lines (for get_payment_session, get_payment_instrument_balance, list_payment_instruments) are unchanged — those tools were already provided by the plugin.

SDK dependency

This commit's system-prompt change is forward-looking; the http_request tool ships in bedrock-agentcore SDK PR aws/bedrock-agentcore-sdk-python#493. Once that merges and a new SDK release ships, the template's pyproject.toml.hbs should pin bedrock-agentcore >= <released-version>. Pin bump intentionally NOT included in this commit — it should land alongside the published version number.

End-to-end verification (after IAM fix + SDK PR)

Tested against https://x402.bitcoinsapi.com/weather on Base Sepolia from a fresh project. Plugin path runs cleanly:

  1. Detected 402 Payment Required response from tool: http_request
  2. Successfully retrieved instrument (← fails without this commit's IAM grant)
  3. Successfully processed payment for user default-user (← fails without SDK PR feat: add agentcore traces command and trace link in invoke TUI #493)
  4. Added payment header to tool input headers: ['PAYMENT-SIGNATURE']
  5. ✅ Tool retried with the payment header

(Final on-chain settle currently fails inside the seller-side facilitator simulation due to two unrelated issues in CDP's signing service — validAfter set to current timestamp instead of now-N, and an ECRecover failure on the returned v byte. Those are tracked separately as service-side bugs and are out of scope for this PR.)

@aidandaly24 aidandaly24 requested a review from a team May 14, 2026 21:32
@github-actions github-actions Bot added the size/xl PR size: XL label May 14, 2026
@github-actions github-actions Bot added the agentcore-harness-reviewing AgentCore Harness review in progress label May 14, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 14, 2026

Package Tarball

aws-agentcore-0.14.2.tgz

How to install

gh release download pr-1261-tarball --repo aws/agentcore-cli --pattern "*.tgz" --dir /tmp/pr-tarball
npm install -g /tmp/pr-tarball/aws-agentcore-0.14.2.tgz

@github-actions github-actions Bot removed the agentcore-harness-reviewing AgentCore Harness review in progress label May 14, 2026
@github-actions github-actions Bot added size/xl PR size: XL and removed size/xl PR size: XL labels May 14, 2026
@github-actions github-actions Bot added size/xl PR size: XL and removed size/xl PR size: XL labels May 15, 2026
@github-actions github-actions Bot added the size/xl PR size: XL label May 15, 2026
if (result.success) {
console.log(JSON.stringify({ success: true }));
} else {
console.log(JSON.stringify({ success: false, error: result.error.message }));
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotta fix this

@github-actions github-actions Bot added size/xl PR size: XL and removed size/xl PR size: XL labels May 19, 2026
@agentcore-devx-automation agentcore-devx-automation Bot added the claude-security-reviewing Claude Code /security-review in progress label May 19, 2026
@agentcore-devx-automation
Copy link
Copy Markdown
Contributor

Claude Security Review: no high-confidence findings. (run)

@agentcore-devx-automation agentcore-devx-automation Bot removed the claude-security-reviewing Claude Code /security-review in progress label May 19, 2026
@github-actions github-actions Bot added size/xl PR size: XL and removed size/xl PR size: XL labels May 20, 2026
…ain.py template

The hasMemory/hasPayment/hasConfigBundle nesting was missing a closing
{{/if}} for the outer hasMemory block, causing Handlebars parse errors
during agentcore create.
…ullish coalescing

- Use serviceEndpoint() instead of hardcoded .amazonaws.com
- Type JSON.parse results as Record<string, unknown> to avoid unsafe any
- Use ?? instead of || for userId fallback
From 15-agent parallel bugbash:
- H1: Use serializeResult() in PaymentManagerPrimitive add handler (JSON {} fix)
- H2: Service principal uses dnsSuffix(region) for multi-partition
- H3: wirePaymentCapability handles BYO agent pattern (Agent() fallback)
- H4: Protocol check moved above auto-session block, autoSession in predicate
- H5: Vended CDK test fixture updated (remove configBundles, add payments)
- M1: remove-all preserves $schema and tags fields
- M2: Template derives connector/role from scoped prefix (not independent scan)
- L1: removeEnvVars writes empty string (not bare newline) when no keys remain
- L2: payment-env.ts guards processPaymentRoleArn before injection
…schema

The CDK's AgentCoreProjectSpecSchema now includes $schema, configBundles,
abTests, and httpGateways fields (matching what actually exists in
agentcore.json). This eliminates all `as any` casts in the vended CDK
template and CLI remove-all handler.

Also updates L3 CDK constructs package with these schema additions.
… to migrate

- Delete payment-iam.ts entirely (addPaymentDenyToExecutionRole was unnecessary
  defense-in-depth that surprises customers with unexpected IAM mutations)
- Delete migratePaymentResources, shouldMigratePayments, cleanupImperativePayment
  (migration path for users that don't exist — feature hasn't shipped)
- Delete ensureProcessPaymentRole, ensureResourceRetrievalRole, deletePaymentRoles
  (CDK constructs handle all IAM role creation)
- Simplify cleanupPaymentCredentialProviders to only handle credential providers
- Remove migration detection block from deploy actions
…d params

- Delete PaymentConnectorResult, PaymentManagerResult, PaymentDeployResult (zero consumers)
- Remove roleCreatedByCli from PaymentDeployedState (CDK manages roles, field is meaningless)
- Remove unused accountId param from SetupPaymentResourcesOptions
- Remove dead migration comment from actions.ts
- Move cleanupPaymentCredentialProviders to static import (AGENTS.md: no inline imports)
… stale code

CRITICAL:
- TUI teardown now cleans up payment credential providers before stack destroy

HIGH:
- Remove dead imperative API exports from barrel (createPaymentManager, etc.)
- Fix "backward compatibility" comment (unreleased feature has no backward compat)

MEDIUM:
- Remove console.error in getOrCreatePaymentSession (silent fallthrough to create)
- Fix stale variable name processor → manager in useCreatePayment
- Fix autoPayment schema to just .optional() (no confusing default+optional combo)
- Fix connector description to mention both providers
- Fix unused catch variables (prefix with _)
…OM_JWT

Fix 1 — Delete dead code:
- Remove createPaymentManager, listPaymentManagers, deletePaymentManager,
  createPaymentConnector, deletePaymentConnector, listPaymentConnectors,
  generateClientToken and ~14 associated type interfaces from agentcore-payments.ts
- These had zero call sites (CDK constructs handle all resource creation)
- Removed ~270 lines of dead code

Fix 2 — Inject AGENTCORE_PAYMENT_{NAME}_AUTH_MODE:
- cdk-stack.ts: inject AUTH_MODE='bearer' when authorizerType is CUSTOM_JWT
- deployed-state.ts: add authorizerType to PaymentDeployedStateSchema
- outputs.ts: pass authorizerType through from spec in parsePaymentOutputs
- actions.ts + useDeployFlow.ts: include authorizerType in paymentSpecs
- payment-env.ts: read authorizerType from project spec for dev mode
- payments.py: read from prefixed env var (${_prefix}AUTH_MODE)

Without this fix, CUSTOM_JWT users always get SigV4 auth mode at runtime.
120 new tests across 7 files (6 new + 1 extended):
- parsePaymentOutputs (23): output key mapping, missing fields, multi-manager
- PaymentManagerPrimitive (20): add/remove/cascade/getRemovable/previewRemove
- PaymentConnectorPrimitive (18): add/remove/composite-key/previewRemove
- validate action.ts (9): all payment error paths in handleValidate
- payment-env (7): dev-mode env var injection + AUTH_MODE
- pre-deploy-payments (15): credential provider create/update/cleanup
- wirePaymentCapability (17): template/BYO patching, idempotency

Total suite: 4036 tests passing.
…alignment

- Fix TUI deploy bug: runPaymentPreDeploy now calls setAllCredentials so
  useDeployFlow.persistDeployedState has correct connector ARNs
- Remove export from 9 dead type interfaces in agentcore-payments.ts
- Rename PaymentCredentialProviderResult → PaymentCredentialProviderApiResult
  to resolve name collision with payment-types.ts
- Fix defaultSpendLimit schema mismatch: CDK now uses z.string().optional()
  matching CLI (was z.object({amount,currency}) — incompatible)
- Remove dead PaymentCredentialProviderResult re-export from barrel
Payment credential providers use the same /identities/ endpoint as API key
and OAuth providers. Move setupPaymentCredentialProviders, hasPaymentCredentialProviders,
and cleanupPaymentCredentialProviders into pre-deploy-identity.ts alongside
the other credential provider operations.

- Delete pre-deploy-payments.ts (merged into pre-deploy-identity.ts)
- Delete payment-types.ts (types inlined in pre-deploy-identity.ts)
- Rename: setupPaymentResources → setupPaymentCredentialProviders
- Rename: hasPaymentManagers → hasPaymentCredentialProviders
- Update all import paths and barrel exports
- Update test imports
- H2: remove abTests/httpGateways from vended cdk.test.ts (not in CDK schema)
- H3: fix double-blank-lines in wirePaymentCapability (regex captured newlines)
- H4: import PAYMENT_SYSTEM_PROMPT and use it in wired Agent constructor
- M1: respect --dry-run flag in `remove all` CLI path (was destructive)
- M2: sanitize underscores from CDK logical IDs (toCdkId helper)
- M4: reject invalid --auto-payment values instead of coercing to true
- M5: require --provider explicitly (no silent CoinbaseCDP default)
- H5: add --json flag to validate command
- L9/L10: validate payment flags early (mutual exclusion, empty strings)
- M6: AUTH_MODE reads from deployed state directly (not project spec)
- M7: remove dead CREDENTIAL_PROVIDER_NAME and per-connector env var injection
- M8: use process.exit(1) instead of process.exitCode = 1 in CDK bin
…l types

Move the payment-specific upfront missing-vars list into a shared
assertEnvFileExists() helper that runs once at deploy start and lists
every required env var across ApiKey, OAuth2, and payment connectors.
Users populating credentials see the full list at once instead of
discovering them piecemeal across separate setup steps.
- Update asset snapshots after mainline updates to template files
- Make getAllCredentials tolerant of projectSpec without payments field
  (some test fixtures don't set it)
The earlier regex used a lookahead that stopped at the wrong newline
boundary, leaving the function body of get_or_create_agent() orphaned at
module scope (a Python SyntaxError on import).

Replace the lookahead with a hard anchor on 'return _agent' since that's
the unambiguous end of the function body.
Found in end-to-end deploy + invoke testing:

1. Vended cdk-stack.ts: grant runtime execution role sts:AssumeRole on
   the ProcessPaymentRole. ProcessPaymentRole's trust policy allows
   AccountRootPrincipal, but the caller still needs sts:AssumeRole on
   its own role. Without this, every invoke that touches payments
   fails with AccessDenied.

2. Vended payments.py: detect whether the installed bedrock-agentcore
   SDK supports the boto3_session field via inspect.signature() before
   passing it. The field was added in 1.11; older published versions
   (1.10 and below) reject the kwarg. Falls back to the runtime role's
   default credentials with a warning when the SDK is too old.
AWS docs (https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/payments-prerequisites.html)
ship the StripePrivy authorization-private-key with a 'wallet-auth:' prefix.
The CLI's base64 validator rejected this with a misleading error message.

Strip the prefix transparently in both CLI and TUI so users can paste the
key straight from the docs.
wirePaymentCapability used to drop a Strands-shaped payments.py and
regex-rewrite main.py for every runtime, regardless of framework. On
LangGraph, GoogleADK, OpenAIAgents, and AutoGen templates the regex
either no-ops or corrupts the agent's Agent() constructor (none of
them accept plugins=). Detect the framework by import signature on
main.py and skip non-Strands runtimes cleanly. Surface a warning on
the add success path listing the skipped runtime names so the user
knows payments must be wired manually for those frameworks.

- PaymentManagerPrimitive.add: collect skippedRuntimes, return in result.
- wirePaymentCapability: read main.py first; bail before any fs writes
  if "from strands import" is absent.
- CLI add: warn when skippedRuntimes is non-empty.
- Tests: 6 new cases for non-Strands gating + missing main.py;
  existing fixtures updated to include the strands import where the
  intent was the Strands wiring path.
The validate JSON output logs result.error.message, which CodeQL flags
because the upstream validation builds error strings from env-var NAMES
(e.g. AGENTCORE_CREDENTIAL_FOO_API_KEY_SECRET — the name of the env var,
not its value). The names are deterministic public strings derived from
the credential name; they never contain credential values. Suppress the
alert with a justification.
…, regex, recovery

- F-01-1: gate payment env-var injection + IAM grants by language (Python)
  and protocol (HTTP). New helper isPaymentEligibleRuntime is used in
  PaymentManagerPrimitive.add, the vended cdk-stack.ts payment loop, and
  dev/payment-env.ts so non-Python or non-HTTP runtimes never get env vars
  they cannot consume.
- F-01-2: emit hooks=[ConfigBundleHook()] alongside plugins= in both the
  template and the regex-emitted Agent block. Prevents existing config-bundle
  customers from silently losing system-prompt injection when adding payments.
- R-13-1: insert the payment import at the top of main.py (after the file
  docstring and any from __future__ imports). Removes a regex that could
  splice the new import inside a parenthesised multi-line from x import (...)
  block and produce a SyntaxError.
- R-13-2: tighten the agent-replacement regexes — allow trailing # type: ignore
  comments, allow typed-annotation _agent: Agent | None = None form, and
  abort cleanly when the call site is replaced but the singleton has an
  unrecognised shape rather than shipping corrupted code.
- S-02-1: re-add after remove now correctly patches main.py. The previous
  early-return short-circuited the whole flow when remove() left payments.py
  behind.
- D-12-2: when CFN succeeds but the post-deploy state-write fails, surface
  the stack name + region + recovery commands so the user can fix the local
  I/O issue or manually delete the orphan stack.
- C-05-3: exclude .env / .env.local / .env.* files from the deploy zip at
  any depth. Closes a footgun for BYO projects with --code-location . where
  agentcore/.env.local could otherwise be packaged into S3.

Tests: 8 new cases covering paren-aware imports, docstring + future,
type-ignore comments, typed annotations, abort path, re-add cycle, and
.env exclusion at any depth.
@agentcore-devx-automation
Copy link
Copy Markdown
Contributor

Claude Security Review: no high-confidence findings. (run)

…prompt update

The deployed agent runtime role was missing every Get/List/Create payment
instrument+session action and ProcessPayment. The L3 stack only granted
sts:AssumeRole on the ProcessPaymentRole, which carries only
ProcessPayment — and the SDK plugin's auto-pay path calls
GetPaymentInstrument on the runtime's own credentials before any role
assumption. Without this grant the plugin fails with AccessDeniedException
on the very first 402 it tries to settle.

Add the seven required actions to the runtime role's inline policy in
the vended CDK stack template (src/assets/cdk/lib/cdk-stack.ts). Scoped
to the manager ARN.

Also update PAYMENT_SYSTEM_PROMPT in the Strands payments capability to
mention the http_request tool, which the AgentCorePaymentsPlugin now
provides automatically (see SDK PR
aws/bedrock-agentcore-sdk-python#493). The other prompt lines for
get_payment_session / get_payment_instrument_balance /
list_payment_instruments are unchanged — those tools were already
provided by the plugin.

Snapshot updated to reflect the cdk-stack.ts addition.
@agentcore-devx-automation
Copy link
Copy Markdown
Contributor

Claude Security Review: no high-confidence findings. (run)

`getOrCreatePaymentSession` was reading `result.paymentSessionId`
directly off the CreatePaymentSession API response. That field doesn't
exist at top level — the service wraps the session in
`paymentSession.paymentSessionId` per the public model
(`bedrock-agentcore` Data Plane). The TypeScript `as` cast doesn't
validate at runtime, so the function silently returned `undefined`,
the auto-session reassignment in handleInvoke set
`options.paymentSessionId = undefined`, and the runtime received an
invoke body without payment_session_id. Plugin then errored with
"payment_session_id is required for x402 payments".

Confirmed against the live API model: CreatePaymentSession output
shape is {paymentSession: {paymentSessionId, paymentManagerArn,
userId, expiryTimeInMinutes, ...}} (note: ListPaymentSessions returns
paymentSessions[] at top level — only Create wraps).

Verified end-to-end on Base Sepolia: with this fix, `agentcore invoke
--auto-session` creates the session, forwards it to the runtime, the
plugin signs via ProcessPayment, and the merchant settles on chain.

Existing unit tests passed because the mocked client returns a stub
shaped however the test wants — they don't enforce the real API
response shape.
@agentcore-devx-automation
Copy link
Copy Markdown
Contributor

Claude Security Review: no high-confidence findings. (run)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/xl PR size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants