Commit 0a8a486
feat(payments): Add LangGraph integration for payment handling (#546)
* feat(payments): Add LangGraph integration for payment handling
* refactor(payments): Unify LangGraph and Strands config into single class
Merge AgentCorePaymentsConfig (LangGraph) and AgentCorePaymentsPluginConfig
(Strands) into a single dataclass in integrations/config.py. Both names
remain available as aliases for backward compatibility.
* define _ERROR_MESSAGES dict at the root level of payments SDK instead of in langgraph-specific package
* added langgraph dependencies to pyproject.toml for CI
* fix: linter check failing due to long lines, wrapped and fixed length of all
* style: apply ruff formatting locally for push and rerun lint check
* fix(payments): Fix broken type import and raise langchain/langgraph version floor
Fix TYPE_CHECKING import in errors.py to use ..config (parent package)
instead of .config (non-existent sibling module). This resolves mypy/pyright
failures for PaymentErrorContext.config type resolution.
Raise langchain and langgraph minimum versions from >=0.2.0 to >=1.0.0 in
both dev dependencies and the [langgraph] optional group.
AgentMiddleware, create_agent, and the langchain.agents.middleware namespace
are langchain 1.0 APIs — the 0.2.0 floor allowed installations that would
fail at import time.
* test(payments): Add unit tests for auto_session lazy session creation
Cover the auto_session feature path that was previously only validated
live against testnet. Tests verify:
- Session created on first 402 when auto_session=True
- Config mutated with new session ID for subsequent calls
- Session reused across multiple tool calls (no duplicate creation)
- auto_session=False still raises PAYMENT ERROR without session
- Budget and expiry config values passed correctly
- Pre-existing session_id skips auto-creation
* docs(payments): Document pre-merge testnet validation requirement
Add PRE-MERGE REQUIREMENT note to test_functional.py docstring clarifying
that any middleware changes must be validated against live testnet before
merge, since these tests are skipped in CI.
* docs(payments): Document middleware instance lifecycle and thread-safety
Add note that one middleware instance should be created per agent
invocation/request. The middleware is not thread-safe due to config
mutations in auto_session and on_payment_error callbacks.
* refactor(payments): Extract shared helpers to reduce sync/async duplication
Extract guard checks, 402 detection, payment request extraction, header
injection, and post-payment rejection detection into shared private
methods. The sync and async paths now only differ at await/sleep
boundaries. Future bug fixes to detection or injection logic only need
to be applied in one place instead of four.
No behavioral changes — 128 tests pass identically before and after.
* docs(payments): Add Sync vs Async section to langgraph middleware README
Document when each path is used (.invoke vs .ainvoke), what the async
path does differently (non-blocking sleep, to_thread for signing, async
callbacks), and provide FastAPI and script examples.
* fix(payments): Detect async callback on sync path and fail loudly
If an async def on_payment_error callback is registered but the agent
runs via .invoke() (sync path), the callback would silently return an
unawaited coroutine, bypass RETRY logic, and leak a RuntimeWarning.
Now we detect this with inspect.iscoroutinefunction() and raise a clear
TypeError that the existing try/except catches and logs — the agent
continues with the default error message and the developer sees exactly
what to fix in their logs.
* test(payments): Cover post-recovery rejection with raw JSON fallback path
Verify that when a raw-JSON tool returns 402 after error handler
recovery, the FallbackHandler is used to extract the real error detail
(e.g. 'budget exceeded') instead of falling through to 'unknown'.
* fix(payments): Reassign handler in post-recovery fallback detection
When fallback detects a 402 in the recovery retry path, reassign _rh to
_FallbackHandler(fallback) so extract_body returns the actual parsed
body. Without this, GenericPaymentHandler looks for the PAYMENT_REQUIRED
marker, finds nothing in raw JSON, and the LLM sees 'unknown' instead
of the real error detail.
* fix(payments): Add name-based handler as third 402 detection fallback
When both GenericPaymentHandler (marker) and _fallback_detect_402 (JSON)
fail to detect a 402, try the name-based handler from get_payment_handler
(e.g. HttpRequestPaymentHandler for tools named http_request). This
covers the legacy 'Status Code: 402' text-block format used by Strands
tools ported to LangGraph without format adaptation.
* fix(payments): Feed custom handlers raw content instead of prepared shape
Custom handlers now receive result.content (the raw ToolMessage content)
for extract_status_code, extract_headers, and extract_body — not the
internal {'content': [{'text': ...}]} prepared shape.
This makes the custom handler contract intuitive: handlers parse the
tool's actual output format, not a middleware-internal wrapper. Fixes
silent detection failures when custom handlers expected raw JSON or
other native formats.
* test(payments): Fill async path and custom handler test coverage gaps
Add tests for:
- Async auto_session creation and reuse
- Async post-payment rejection with raw JSON fallback
- Async name-based handler fallback (legacy text-block format)
- Async custom handler receiving raw content
- Async error handler callback (retry and propagate paths)
- Sync custom handler raw content contract verification
Suite now at 141 passed, providing async path parity with sync tests.
* refactor(payments): Collapse remaining sync/async duplication
- Merge _check_post_payment_rejection and _check_post_recovery_rejection
into single _check_retry_rejection with context parameter
- Extract _inject_for_error_retry for shared injection in error handlers
- Extract _build_error_context for shared PaymentErrorContext construction
- Extract _handle_callback_resolution for shared resolution dispatch
Reduces middleware.py from 805 to 704 lines. The sync/async error
handlers now only differ at await/sleep/to_thread boundaries.
141 tests pass identically.
* refactor(payments): Hoist all inline imports to module top
Move asyncio, json, GenericPaymentHandler, ErrorResolution, and
PaymentErrorContext imports to the top of the file. None have circular
dependency risks. Eliminates repeated imports that contributed to
drift between duplicated code blocks.
* fix(payments): Update functional test custom handlers for raw content contract
Update TrackingHandler and RawJsonHandler in test_functional.py to
handle raw content (str/list) instead of the prepared shape dict.
Required after the custom handler contract change in 076d6ed.
---------
Co-authored-by: Aidan Daly <99039782+aidandaly24@users.noreply.github.com>1 parent ba755aa commit 0a8a486
18 files changed
Lines changed: 5053 additions & 58 deletions
File tree
- src/bedrock_agentcore/payments/integrations
- langgraph
- tests/bedrock_agentcore/payments/integrations/langgraph
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
151 | 151 | | |
152 | 152 | | |
153 | 153 | | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
154 | 157 | | |
155 | 158 | | |
156 | 159 | | |
| |||
163 | 166 | | |
164 | 167 | | |
165 | 168 | | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
166 | 175 | | |
167 | 176 | | |
168 | 177 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
2 | 2 | | |
3 | | - | |
4 | | - | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
5 | 7 | | |
6 | 8 | | |
7 | 9 | | |
8 | 10 | | |
9 | | - | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
10 | 14 | | |
11 | 15 | | |
12 | | - | |
13 | | - | |
| 16 | + | |
14 | 17 | | |
15 | 18 | | |
16 | 19 | | |
17 | 20 | | |
18 | 21 | | |
19 | 22 | | |
20 | 23 | | |
21 | | - | |
22 | | - | |
23 | | - | |
24 | | - | |
25 | | - | |
26 | | - | |
27 | | - | |
28 | | - | |
29 | | - | |
30 | | - | |
31 | | - | |
32 | | - | |
33 | | - | |
34 | | - | |
35 | | - | |
36 | | - | |
37 | | - | |
38 | | - | |
39 | | - | |
40 | | - | |
41 | | - | |
42 | | - | |
43 | | - | |
44 | | - | |
45 | | - | |
46 | | - | |
47 | | - | |
48 | | - | |
49 | | - | |
50 | | - | |
51 | | - | |
52 | | - | |
53 | | - | |
54 | | - | |
55 | | - | |
56 | | - | |
57 | | - | |
58 | | - | |
59 | | - | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
60 | 47 | | |
61 | 48 | | |
62 | 49 | | |
| |||
65 | 52 | | |
66 | 53 | | |
67 | 54 | | |
68 | | - | |
| 55 | + | |
69 | 56 | | |
70 | | - | |
71 | 57 | | |
72 | 58 | | |
73 | 59 | | |
74 | 60 | | |
75 | 61 | | |
76 | 62 | | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
77 | 70 | | |
78 | 71 | | |
79 | 72 | | |
80 | 73 | | |
81 | 74 | | |
82 | | - | |
83 | 75 | | |
84 | 76 | | |
85 | 77 | | |
| 78 | + | |
| 79 | + | |
86 | 80 | | |
87 | 81 | | |
88 | | - | |
89 | 82 | | |
90 | 83 | | |
91 | 84 | | |
92 | | - | |
93 | | - | |
94 | | - | |
95 | 85 | | |
96 | 86 | | |
| 87 | + | |
| 88 | + | |
97 | 89 | | |
98 | 90 | | |
99 | 91 | | |
100 | | - | |
101 | | - | |
102 | | - | |
| 92 | + | |
| 93 | + | |
103 | 94 | | |
104 | 95 | | |
105 | 96 | | |
106 | 97 | | |
107 | 98 | | |
108 | 99 | | |
109 | 100 | | |
110 | | - | |
111 | | - | |
112 | | - | |
113 | 101 | | |
114 | 102 | | |
115 | 103 | | |
| |||
122 | 110 | | |
123 | 111 | | |
124 | 112 | | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
125 | 131 | | |
126 | 132 | | |
127 | 133 | | |
| |||
141 | 147 | | |
142 | 148 | | |
143 | 149 | | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
Lines changed: 74 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
0 commit comments