Commit d5428b2
authored
fix(payments): drop unsupported paymentConnectorId + add http_request plugin tool + EIP-3009 timing fix (#493)
* fix(payments): drop unsupported paymentConnectorId from ProcessPayment call
ProcessPayment's public API contract (bedrock-agentcore Data Plane) does
not accept a paymentConnectorId field — the connector is resolved server
side from the payment instrument. The SDK was forwarding the param,
causing every real ProcessPayment call to fail at the boto3 layer with:
Parameter validation failed:
Unknown parameter in input: "paymentConnectorId", must be one of:
userId, agentName, paymentManagerArn, paymentSessionId,
paymentInstrumentId, paymentType, paymentInput, clientToken
The bug surfaced end-to-end when AgentCorePaymentsPlugin auto-paid a
402 response: get_payment_instrument succeeded, then generate_payment_header
forwarded payment_connector_id into process_payment, which appended it to
the API call and was rejected. Unit tests passed because they mock the
boto3 client and don't validate the param schema.
Fix: stop including paymentConnectorId in the ProcessPayment request body,
and stop forwarding it from generate_payment_header. The kwarg is kept on
both function signatures for backward compatibility, with docstrings
updated to note that it is accepted but no longer forwarded.
Adds a regression test asserting paymentConnectorId is absent from the
ProcessPayment kwargs even when callers pass it explicitly.
* feat(payments): add http_request tool to AgentCorePaymentsPlugin
Ships a Strands @tool method on AgentCorePaymentsPlugin that performs HTTP
requests and emits Strands ToolResult content blocks the SDK's payment
handlers can parse. When the endpoint returns 402 Payment Required, the
tool prefixes the content block with the spec-compliant PAYMENT_REQUIRED:
marker, so the plugin's existing after_tool_call hook intercepts the
result, generates an x402 payment header via PaymentManager, and Strands
re-invokes the tool with the payment header injected.
Why on the plugin: aligns with how get_payment_instrument /
list_payment_instruments / get_payment_session are already exposed —
adding the plugin to a Strands agent now produces a turnkey paid-HTTP
experience without requiring strands-agents-tools or hand-written httpx
code in customer agents.
httpx is already a hard SDK dep; no new dependencies are introduced.
The 402 envelope (statusCode + headers + body) matches what
GenericPaymentHandler / HttpRequestPaymentHandler already parse, so the
existing handler dispatch lights up automatically.
Tests cover:
- 200 envelope shape (Strands ToolResult dict, not a raw list)
- 402 wrapped with PAYMENT_REQUIRED: marker
- End-to-end contract: tool output -> get_payment_handler -> 402
- GET ignores body, POST sends dict as JSON, str body sent verbatim
- Caller header dict isolated (mutations don't bleed back)
- Non-JSON body falls back to {"text": ...}
- httpx.RequestError returned as status='error' (not raised)
- Method casing normalization
- Default headers when caller omits
All 490 payments tests pass.
* style(payments): apply ruff format to plugin + tests
* feat(payments): provide_http_request opt-out flag for AgentCorePaymentsPlugin
Strands' tool registry raises ValueError on duplicate tool names, so a
caller who wants to ship their own http_request currently can't — the
plugin's auto-discovered http_request crashes Agent construction first.
Add provide_http_request: bool = True to AgentCorePaymentsPluginConfig.
When True (default), behavior is unchanged: the plugin's http_request
@tool is auto-registered. When False, the plugin filters its own
http_request out of self._tools right after super().__init__() so the
caller can pass their own without a name collision.
Auto-payment of 402 responses still works against any tool whose output
emits the PAYMENT_REQUIRED: content marker — the after_tool_call hook
is independent of the tool registration, so disabling the flag does
not disable interception.
Adds 5 tests:
- default registers http_request
- opt-out drops only http_request (other plugin tools intact)
- opt-out preserves the after_tool_call/before_tool_call hooks
- config validator rejects non-bool values for the flag
- init_agent still works with the flag off
All 495 payments tests pass.
* feat(payments): add post_payment_retry_delay_seconds to fix EIP-3009 timing race
The x402 EIP-3009 transferWithAuthorization contract requires
block.timestamp > validAfter — a strict greater-than check, not
greater-or-equal. When the signing service mints a signature with
validAfter set to ~current Unix time, fast merchant facilitators submit
the transaction within the same second the signature was minted, hitting
the contract's strict check before block.timestamp advances. The seller
returns a misleading 402 with reason "invalid_payload" or
"invalid_exact_evm_transaction_simulation_failed" — but the signature
is structurally valid; it's a race between minting and submission.
Verified empirically: same signed payload reverts via eth_call within
the validAfter second, then succeeds 5+ seconds later. Once the chain
advances one block past validAfter, the seller's facilitator submits and
the on-chain transferWithAuthorization settles. Real Base Sepolia
transaction proving the round-trip:
0x0e542e2d5c3c97521bd47231d299c6d56841fe0865f63ebfaa92d6f47e28b6b6
Add post_payment_retry_delay_seconds: float = 3.0 to
AgentCorePaymentsPluginConfig. Sleeps that many seconds in
after_tool_call right before setting event.retry = True, so by the time
Strands re-invokes the tool with the PAYMENT-SIGNATURE header, the
chain has advanced one block past validAfter.
3.0s default chosen as ~one Base Sepolia block (~2s) plus margin. Set
to 0 to disable for tests or chains with sub-second blocks.
Adds 7 tests:
- default delay is 3.0
- custom positive delay is honored
- zero delay skips the sleep entirely (event.retry still set)
- no sleep when signing fails (we never reach the retry path)
- validator rejects negative values
- validator rejects non-numeric values (str)
- validator rejects bool (which would otherwise sneak through isinstance(int))
All 502 payments tests pass.1 parent 29287c2 commit d5428b2
6 files changed
Lines changed: 697 additions & 18 deletions
File tree
- src/bedrock_agentcore/payments
- integrations
- strands
- tests/bedrock_agentcore/payments
- integrations/strands
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
39 | 39 | | |
40 | 40 | | |
41 | 41 | | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
42 | 60 | | |
43 | 61 | | |
44 | 62 | | |
| |||
54 | 72 | | |
55 | 73 | | |
56 | 74 | | |
| 75 | + | |
| 76 | + | |
57 | 77 | | |
58 | 78 | | |
59 | 79 | | |
| |||
87 | 107 | | |
88 | 108 | | |
89 | 109 | | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
90 | 125 | | |
91 | 126 | | |
92 | 127 | | |
| |||
Lines changed: 116 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
| 3 | + | |
3 | 4 | | |
| 5 | + | |
4 | 6 | | |
5 | | - | |
| 7 | + | |
6 | 8 | | |
| 9 | + | |
7 | 10 | | |
8 | 11 | | |
9 | 12 | | |
| |||
25 | 28 | | |
26 | 29 | | |
27 | 30 | | |
28 | | - | |
| 31 | + | |
| 32 | + | |
29 | 33 | | |
30 | 34 | | |
31 | 35 | | |
| |||
55 | 59 | | |
56 | 60 | | |
57 | 61 | | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
58 | 74 | | |
59 | 75 | | |
60 | 76 | | |
| |||
236 | 252 | | |
237 | 253 | | |
238 | 254 | | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
239 | 269 | | |
240 | 270 | | |
241 | 271 | | |
| |||
726 | 756 | | |
727 | 757 | | |
728 | 758 | | |
| 759 | + | |
| 760 | + | |
| 761 | + | |
| 762 | + | |
| 763 | + | |
| 764 | + | |
| 765 | + | |
| 766 | + | |
| 767 | + | |
| 768 | + | |
| 769 | + | |
| 770 | + | |
| 771 | + | |
| 772 | + | |
| 773 | + | |
| 774 | + | |
| 775 | + | |
| 776 | + | |
| 777 | + | |
| 778 | + | |
| 779 | + | |
| 780 | + | |
| 781 | + | |
| 782 | + | |
| 783 | + | |
| 784 | + | |
| 785 | + | |
| 786 | + | |
| 787 | + | |
| 788 | + | |
| 789 | + | |
| 790 | + | |
| 791 | + | |
| 792 | + | |
| 793 | + | |
| 794 | + | |
| 795 | + | |
| 796 | + | |
| 797 | + | |
| 798 | + | |
| 799 | + | |
| 800 | + | |
| 801 | + | |
| 802 | + | |
| 803 | + | |
| 804 | + | |
| 805 | + | |
| 806 | + | |
| 807 | + | |
| 808 | + | |
| 809 | + | |
| 810 | + | |
| 811 | + | |
| 812 | + | |
| 813 | + | |
| 814 | + | |
| 815 | + | |
| 816 | + | |
| 817 | + | |
| 818 | + | |
| 819 | + | |
| 820 | + | |
| 821 | + | |
| 822 | + | |
| 823 | + | |
| 824 | + | |
| 825 | + | |
| 826 | + | |
| 827 | + | |
| 828 | + | |
| 829 | + | |
| 830 | + | |
| 831 | + | |
| 832 | + | |
| 833 | + | |
| 834 | + | |
| 835 | + | |
| 836 | + | |
| 837 | + | |
| 838 | + | |
| 839 | + | |
| 840 | + | |
| 841 | + | |
| 842 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
844 | 844 | | |
845 | 845 | | |
846 | 846 | | |
847 | | - | |
| 847 | + | |
| 848 | + | |
| 849 | + | |
| 850 | + | |
848 | 851 | | |
849 | 852 | | |
850 | 853 | | |
| |||
873 | 876 | | |
874 | 877 | | |
875 | 878 | | |
876 | | - | |
877 | | - | |
| 879 | + | |
| 880 | + | |
| 881 | + | |
878 | 882 | | |
879 | 883 | | |
880 | 884 | | |
| |||
935 | 939 | | |
936 | 940 | | |
937 | 941 | | |
938 | | - | |
| 942 | + | |
| 943 | + | |
| 944 | + | |
| 945 | + | |
939 | 946 | | |
940 | 947 | | |
941 | 948 | | |
| |||
1045 | 1052 | | |
1046 | 1053 | | |
1047 | 1054 | | |
1048 | | - | |
1049 | | - | |
1050 | | - | |
1051 | | - | |
1052 | | - | |
1053 | | - | |
1054 | | - | |
1055 | | - | |
1056 | | - | |
1057 | | - | |
1058 | | - | |
1059 | | - | |
| 1055 | + | |
| 1056 | + | |
| 1057 | + | |
| 1058 | + | |
| 1059 | + | |
| 1060 | + | |
| 1061 | + | |
| 1062 | + | |
| 1063 | + | |
| 1064 | + | |
| 1065 | + | |
| 1066 | + | |
1060 | 1067 | | |
1061 | 1068 | | |
1062 | 1069 | | |
| |||
0 commit comments