Skip to content

Commit b130f9a

Browse files
committed
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.
1 parent de290ab commit b130f9a

3 files changed

Lines changed: 114 additions & 0 deletions

File tree

src/bedrock_agentcore/payments/integrations/config.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ class AgentCorePaymentsPluginConfig:
3939
eligible (preserving existing behavior). When set, only tool calls whose
4040
name appears in this list will trigger payment processing; all others are
4141
skipped.
42+
provide_http_request: Whether the plugin should register its built-in
43+
``http_request`` ``@tool`` on the agent. Defaults to True so adding the
44+
plugin gives a turnkey paid-HTTP experience. Set to False if you want
45+
to ship your own ``http_request`` tool — Strands raises a ValueError
46+
on duplicate tool names, so you must opt out of the plugin's version
47+
before passing your own. Auto-payment of 402 responses still works
48+
against any tool whose output carries the ``PAYMENT_REQUIRED:``
49+
content marker, so disabling this flag does not disable interception.
4250
"""
4351

4452
payment_manager_arn: str
@@ -54,6 +62,7 @@ class AgentCorePaymentsPluginConfig:
5462
bearer_token: Optional[str] = None
5563
token_provider: Optional[Callable[[], str]] = None
5664
payment_tool_allowlist: Optional[List[str]] = None
65+
provide_http_request: bool = True
5766

5867
def __post_init__(self) -> None:
5968
"""Validate configuration after initialization."""
@@ -87,6 +96,9 @@ def __post_init__(self) -> None:
8796
if not all(isinstance(t, str) for t in self.payment_tool_allowlist):
8897
raise ValueError("All entries in payment_tool_allowlist must be strings")
8998

99+
if not isinstance(self.provide_http_request, bool):
100+
raise ValueError(f"provide_http_request must be a boolean, got {type(self.provide_http_request).__name__}")
101+
90102
def update_payment_session_id(self, payment_session_id: str) -> None:
91103
"""Update the payment session ID.
92104

src/bedrock_agentcore/payments/integrations/strands/plugin.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,18 @@ def __init__(self, config: AgentCorePaymentsPluginConfig):
5858
super().__init__()
5959
self.config = config
6060
self.payment_manager: Optional[PaymentManager] = None
61+
62+
# Honor the provide_http_request opt-out: Strands' Plugin base auto-discovers
63+
# every @tool method into self._tools at super().__init__(). If the caller
64+
# wants to ship their own http_request, drop ours so Strands' tool registry
65+
# doesn't raise ValueError on duplicate tool name.
66+
if not self.config.provide_http_request:
67+
self._tools = [t for t in self._tools if t.tool_name != "http_request"]
68+
logger.info(
69+
"provide_http_request=False — plugin's http_request tool will not be registered. "
70+
"Auto-payment still triggers on any tool emitting a PAYMENT_REQUIRED: marker."
71+
)
72+
6173
logger.info("Initialized AgentCorePaymentsPlugin")
6274

6375
def init_agent(self, agent) -> None:

tests/bedrock_agentcore/payments/integrations/strands/test_tools.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1917,3 +1917,93 @@ def test_default_headers_dict_when_caller_omits(self, mock_client_cls):
19171917
sent = mock_client.request.call_args.kwargs["headers"]
19181918
assert isinstance(sent, dict)
19191919
assert sent == {}
1920+
1921+
1922+
class TestProvideHttpRequestOptOut:
1923+
"""Tests for the provide_http_request config flag.
1924+
1925+
When True (default), AgentCorePaymentsPlugin registers its built-in
1926+
http_request @tool. When False, the tool is dropped from the plugin's
1927+
discovered tools so callers can ship their own http_request without
1928+
Strands raising ValueError on duplicate tool names.
1929+
"""
1930+
1931+
@staticmethod
1932+
def _make_config(**overrides):
1933+
from bedrock_agentcore.payments.integrations.config import AgentCorePaymentsPluginConfig
1934+
1935+
kwargs = dict(
1936+
payment_manager_arn="arn:aws:payment:us-east-1:123456789012:payment-manager/test",
1937+
user_id="test-user",
1938+
payment_instrument_id="test-instrument",
1939+
payment_session_id="test-session",
1940+
)
1941+
kwargs.update(overrides)
1942+
return AgentCorePaymentsPluginConfig(**kwargs)
1943+
1944+
def test_default_provides_http_request(self):
1945+
"""By default the plugin registers http_request alongside its other tools."""
1946+
from bedrock_agentcore.payments.integrations.strands.plugin import AgentCorePaymentsPlugin
1947+
1948+
plugin = AgentCorePaymentsPlugin(self._make_config())
1949+
tool_names = {t.tool_name for t in plugin.tools}
1950+
assert "http_request" in tool_names
1951+
# Sanity: the rest of the plugin's tools are still there.
1952+
assert "get_payment_instrument" in tool_names
1953+
assert "get_payment_session" in tool_names
1954+
1955+
def test_opt_out_drops_only_http_request(self):
1956+
"""provide_http_request=False removes ONLY http_request from the registered tools."""
1957+
from bedrock_agentcore.payments.integrations.strands.plugin import AgentCorePaymentsPlugin
1958+
1959+
plugin = AgentCorePaymentsPlugin(self._make_config(provide_http_request=False))
1960+
tool_names = {t.tool_name for t in plugin.tools}
1961+
assert "http_request" not in tool_names
1962+
# Other plugin tools must still be present so payment-info queries still work.
1963+
assert "get_payment_instrument" in tool_names
1964+
assert "list_payment_instruments" in tool_names
1965+
assert "get_payment_instrument_balance" in tool_names
1966+
assert "get_payment_session" in tool_names
1967+
1968+
def test_opt_out_does_not_disable_payment_hook(self):
1969+
"""Disabling the tool must not disable the after_tool_call interceptor.
1970+
1971+
A caller's own http_request that emits PAYMENT_REQUIRED-marked content
1972+
should still trigger the plugin's auto-pay path.
1973+
"""
1974+
from bedrock_agentcore.payments.integrations.strands.plugin import AgentCorePaymentsPlugin
1975+
1976+
plugin = AgentCorePaymentsPlugin(self._make_config(provide_http_request=False))
1977+
hook_names = [getattr(h, "__name__", repr(h)) for h in plugin.hooks]
1978+
assert "after_tool_call" in hook_names
1979+
assert "before_tool_call" in hook_names
1980+
1981+
def test_provide_http_request_validation_rejects_non_bool(self):
1982+
"""Config validator rejects non-boolean values for the new flag."""
1983+
import pytest
1984+
1985+
from bedrock_agentcore.payments.integrations.config import AgentCorePaymentsPluginConfig
1986+
1987+
with pytest.raises(ValueError, match="provide_http_request must be a boolean"):
1988+
AgentCorePaymentsPluginConfig(
1989+
payment_manager_arn="arn:aws:payment:us-east-1:123456789012:payment-manager/test",
1990+
user_id="test-user",
1991+
provide_http_request="yes", # type: ignore[arg-type]
1992+
)
1993+
1994+
def test_opt_out_preserves_payment_manager_init(self):
1995+
"""The init flow (PaymentManager initialization) is unaffected by the flag."""
1996+
from unittest.mock import MagicMock
1997+
1998+
from bedrock_agentcore.payments.integrations.strands.plugin import AgentCorePaymentsPlugin
1999+
2000+
plugin = AgentCorePaymentsPlugin(self._make_config(provide_http_request=False))
2001+
# init_agent should be callable and should not blow up just because we opted out.
2002+
try:
2003+
plugin.init_agent(agent=MagicMock())
2004+
except RuntimeError:
2005+
# Real PaymentManager call against a fake ARN — tolerate the network/IAM
2006+
# failure; what we care about is that init_agent doesn't trip on the flag.
2007+
pass
2008+
# Plugin object itself must still be usable
2009+
assert plugin.config.provide_http_request is False

0 commit comments

Comments
 (0)