From 9ded16b1438f3fc234c03927cf8055f03d6de497 Mon Sep 17 00:00:00 2001 From: vvillait88 Date: Thu, 14 May 2026 11:37:35 -0700 Subject: [PATCH] flatten payment_required_header to kwargs; delete PaymentRequiredHeaderInput MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drops the wrapper for the x402 PAYMENT-REQUIRED header builder: - payment_required_header(PaymentRequiredHeaderInput(x402_version=..., accepts=..., resource=...)) → payment_required_header(*, x402_version, accepts, resource=None) Internal callers updated: - challenge/respond_402.py — drops the wrapper-bridge introduced in 303a; respond_402's public `x402: dict` kwarg now flows straight through. - payment/headers.py — build_payment_headers' x402 branch - discovery/probe.py — discovery probe's PAYMENT-REQUIRED header Deleted from public exports: PaymentRequiredHeaderInput. Tests: 1031 passed / 3 skipped, 95.04% coverage. ty + ruff clean. Vulture: only the known false positives remain (string-form cast + Protocol method param in untouched modules). Co-Authored-By: Claude Opus 4.7 (1M context) --- agentscore_commerce/challenge/respond_402.py | 7 ++----- agentscore_commerce/discovery/probe.py | 7 ++----- agentscore_commerce/payment/__init__.py | 2 -- agentscore_commerce/payment/headers.py | 9 +++----- .../payment/wwwauthenticate.py | 21 ++++++++----------- tests/test_payment_misc.py | 11 +++------- 6 files changed, 19 insertions(+), 38 deletions(-) diff --git a/agentscore_commerce/challenge/respond_402.py b/agentscore_commerce/challenge/respond_402.py index 0e92d77..4d8d0a1 100644 --- a/agentscore_commerce/challenge/respond_402.py +++ b/agentscore_commerce/challenge/respond_402.py @@ -30,7 +30,7 @@ from dataclasses import dataclass from typing import Any -from agentscore_commerce.payment.wwwauthenticate import PaymentRequiredHeaderInput, payment_required_header +from agentscore_commerce.payment.wwwauthenticate import payment_required_header @dataclass @@ -61,8 +61,5 @@ def respond_402( headers = {k.lower(): v for k, v in mppx_challenge_headers.items()} headers["content-type"] = "application/json" if x402 is not None: - # PaymentRequiredHeaderInput still exists pending the wwwauthenticate - # flatten in a subsequent PR; respond_402's public API takes a dict now - # and we adapt internally so the wrapper deletion is invisible to callers. - headers["payment-required"] = payment_required_header(PaymentRequiredHeaderInput(**x402)) + headers["payment-required"] = payment_required_header(**x402) return Respond402Result(body=body, headers=headers, status=402) diff --git a/agentscore_commerce/discovery/probe.py b/agentscore_commerce/discovery/probe.py index d5f925c..d2e17ab 100644 --- a/agentscore_commerce/discovery/probe.py +++ b/agentscore_commerce/discovery/probe.py @@ -12,10 +12,7 @@ ) from agentscore_commerce.payment.networks import networks from agentscore_commerce.payment.usdc import USDC -from agentscore_commerce.payment.wwwauthenticate import ( - PaymentRequiredHeaderInput, - payment_required_header, -) +from agentscore_commerce.payment.wwwauthenticate import payment_required_header # Placeholder payTo for x402 sample accepts in the discovery probe — the probe # exists for crawlers to find that we support x402, not for actual payment. @@ -156,7 +153,7 @@ def build_discovery_probe_response(opts: DiscoveryProbeOptions) -> DiscoveryProb "url": opts.x402_sample.resource_url, "mimeType": "application/json", } - encoded = payment_required_header(PaymentRequiredHeaderInput(**header_kwargs)) + encoded = payment_required_header(**header_kwargs) headers["payment-required"] = encoded # Mirror the aliased accepts in the body so clients that fall back from # header → body (e.g. awal's discover) can still extract requirements. diff --git a/agentscore_commerce/payment/__init__.py b/agentscore_commerce/payment/__init__.py index b030c23..f966b4b 100644 --- a/agentscore_commerce/payment/__init__.py +++ b/agentscore_commerce/payment/__init__.py @@ -36,7 +36,6 @@ ) from agentscore_commerce.payment.usdc import USDC from agentscore_commerce.payment.wwwauthenticate import ( - PaymentRequiredHeaderInput, alias_amount_fields, payment_required_header, www_authenticate_header, @@ -87,7 +86,6 @@ "NetworkFamily", "PaymentHeadersRail", "PaymentHeadersResult", - "PaymentRequiredHeaderInput", "PaymentSigner", "ProcessX402SettleFailure", "ProcessX402SettleResult", diff --git a/agentscore_commerce/payment/headers.py b/agentscore_commerce/payment/headers.py index 93dcd54..4e34f4b 100644 --- a/agentscore_commerce/payment/headers.py +++ b/agentscore_commerce/payment/headers.py @@ -16,7 +16,6 @@ from agentscore_commerce.payment.directive import build_payment_directive from agentscore_commerce.payment.wwwauthenticate import ( - PaymentRequiredHeaderInput, payment_required_header, www_authenticate_header, ) @@ -138,11 +137,9 @@ def build_payment_headers( if x402 is not None: result["payment_required"] = payment_required_header( - PaymentRequiredHeaderInput( - x402_version=x402.version, - accepts=x402.accepts, - resource=x402.resource, - ), + x402_version=x402.version, + accepts=x402.accepts, + resource=x402.resource, ) return result diff --git a/agentscore_commerce/payment/wwwauthenticate.py b/agentscore_commerce/payment/wwwauthenticate.py index b0d20ae..361bd49 100644 --- a/agentscore_commerce/payment/wwwauthenticate.py +++ b/agentscore_commerce/payment/wwwauthenticate.py @@ -2,7 +2,6 @@ import base64 import json -from dataclasses import dataclass from typing import Any, Literal @@ -39,21 +38,19 @@ def alias_amount_fields(accepts: list[Any]) -> list[Any]: return out -@dataclass -class PaymentRequiredHeaderInput: - x402_version: Literal[1, 2] - accepts: list[Any] - resource: dict[str, str] | None = None - - -def payment_required_header(input: PaymentRequiredHeaderInput) -> str: +def payment_required_header( + *, + x402_version: Literal[1, 2], + accepts: list[Any], + resource: dict[str, str] | None = None, +) -> str: """Encode the standard x402 PAYMENT-REQUIRED header (base64-encoded JSON). Each accepts entry is post-processed via :func:`alias_amount_fields` so v1-only clients (e.g. awal) and v2-strict clients can both read it. """ - body: dict[str, Any] = {"x402Version": input.x402_version, "accepts": alias_amount_fields(input.accepts)} - if input.resource is not None: - body["resource"] = input.resource + body: dict[str, Any] = {"x402Version": x402_version, "accepts": alias_amount_fields(accepts)} + if resource is not None: + body["resource"] = resource raw = json.dumps(body, separators=(",", ":")).encode() return base64.b64encode(raw).decode() diff --git a/tests/test_payment_misc.py b/tests/test_payment_misc.py index 17f56af..27fde18 100644 --- a/tests/test_payment_misc.py +++ b/tests/test_payment_misc.py @@ -4,7 +4,6 @@ from agentscore_commerce.payment import ( SETTLEMENT_OVERRIDES_HEADER, USDC, - PaymentRequiredHeaderInput, lookup_rail, network_family, networks, @@ -58,9 +57,7 @@ def test_www_authenticate_header_joins_directives(): def test_payment_required_header_base64_encodes_json(): - h = payment_required_header( - PaymentRequiredHeaderInput(x402_version=2, accepts=[{"scheme": "exact"}], resource={"url": "https://x"}) - ) + h = payment_required_header(x402_version=2, accepts=[{"scheme": "exact"}], resource={"url": "https://x"}) decoded = json.loads(base64.b64decode(h)) assert decoded["x402Version"] == 2 assert decoded["accepts"] == [{"scheme": "exact"}] @@ -75,10 +72,8 @@ def test_payment_required_header_emits_v1_alias_for_v2_clients(): from agentscore_commerce.payment import alias_amount_fields h = payment_required_header( - PaymentRequiredHeaderInput( - x402_version=2, - accepts=[{"scheme": "exact", "network": "eip155:84532", "amount": "110000"}], - ) + x402_version=2, + accepts=[{"scheme": "exact", "network": "eip155:84532", "amount": "110000"}], ) decoded = json.loads(base64.b64decode(h)) assert decoded["accepts"][0]["amount"] == "110000"