open-agentpay 0.2.1__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/PKG-INFO +1 -1
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/pyproject.toml +1 -1
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/__init__.py +1 -1
- open_agentpay-0.2.2/src/agentpay/client/client.py +170 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/client/transport.py +89 -7
- {open_agentpay-0.2.1/src/agentpay/protocol → open_agentpay-0.2.2/src/agentpay/core}/codecs.py +10 -1
- {open_agentpay-0.2.1/src/agentpay/protocol → open_agentpay-0.2.2/src/agentpay/core}/models.py +74 -12
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/policy/models.py +11 -2
- open_agentpay-0.2.1/src/agentpay/client/client.py +0 -109
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/.gitignore +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/README.md +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/budget/__init__.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/client/__init__.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/client/errors.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/client/interfaces.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/client/signer.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/core/__init__.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/core/errors.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/core/interop.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/core/state.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/evm/__init__.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/evm/constants.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/evm/eip3009.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/evm/rpc.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/integrations/__init__.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/integrations/langchain/__init__.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/integrations/langchain/http.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/integrations/langchain/tool.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/policy/__init__.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/policy/config.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/policy/db.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/policy/errors.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/policy/evaluator.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/policy/interfaces.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/policy/metered.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/policy/sqlite_engine.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/protocol/__init__.py +0 -0
- {open_agentpay-0.2.1/src/agentpay/core → open_agentpay-0.2.2/src/agentpay/protocol}/codecs.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/protocol/errors.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/protocol/interop.py +0 -0
- {open_agentpay-0.2.1/src/agentpay/core → open_agentpay-0.2.2/src/agentpay/protocol}/models.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/protocol/state.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/signer/__init__.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/state/__init__.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/state/buyer_coordinator.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/state/errors.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/state/guards.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/state/reconciliation.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/testing/__init__.py +0 -0
- {open_agentpay-0.2.1 → open_agentpay-0.2.2}/src/agentpay/testing/mock_seller.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: open-agentpay
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: The Open-Source Payment Infrastructure & Financial Firewall for Autonomous AI Agents
|
|
5
5
|
Project-URL: Homepage, https://github.com/open-agentpay/AgentPay
|
|
6
6
|
Project-URL: Documentation, https://open-agentpay.github.io/AgentPay/
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AgentPayClient: Drop-in httpx.Client with automatic 402 payment negotiation.
|
|
3
|
+
|
|
4
|
+
Wraps the financial firewall and EIP-712 cryptographic signer to automatically
|
|
5
|
+
satisfy HTTP 402 Payment Required challenges.
|
|
6
|
+
|
|
7
|
+
Note on ``transport=``: unlike a plain httpx.Client, the transport you pass here is the
|
|
8
|
+
INNER transport. It is always wrapped by AgentPayTransport so every request passes the firewall.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from collections.abc import Callable
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
import httpx
|
|
17
|
+
|
|
18
|
+
from agentpay.client.interfaces import ISigner
|
|
19
|
+
from agentpay.client.transport import (
|
|
20
|
+
DEFAULT_CREDENTIAL_HEADERS,
|
|
21
|
+
DEFAULT_CREDENTIAL_VALUE_PREFIXES,
|
|
22
|
+
AgentPayTransport,
|
|
23
|
+
AsyncAgentPayTransport,
|
|
24
|
+
CredentialMode,
|
|
25
|
+
)
|
|
26
|
+
from agentpay.policy.interfaces import IPolicyEngine
|
|
27
|
+
|
|
28
|
+
# Client kwargs that configure the default network transport. httpx only applies them when it builds
|
|
29
|
+
# the transport itself; since we always inject ours, they must be forwarded to the inner transport.
|
|
30
|
+
_INNER_PASSTHROUGH = ("verify", "cert", "http1", "http2", "limits", "trust_env")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _inner_transport_kwargs(kwargs: dict[str, Any]) -> dict[str, Any]:
|
|
34
|
+
"""Collect inner-transport settings from client kwargs (mutates ``kwargs``: removes ``proxy``)."""
|
|
35
|
+
if "mounts" in kwargs or "proxies" in kwargs:
|
|
36
|
+
# httpx routes matching URLs to mounted transports BEFORE the default transport,
|
|
37
|
+
# which would skip the payment firewall entirely.
|
|
38
|
+
raise TypeError(
|
|
39
|
+
"AgentPayClient does not support 'mounts'/'proxies': they bypass the payment firewall. "
|
|
40
|
+
"Pass a pre-configured inner transport via transport=... or use proxy=..."
|
|
41
|
+
)
|
|
42
|
+
out = {k: kwargs[k] for k in _INNER_PASSTHROUGH if k in kwargs}
|
|
43
|
+
if "proxy" in kwargs:
|
|
44
|
+
# Left in kwargs, httpx would create a proxy mount that bypasses AgentPayTransport.
|
|
45
|
+
out["proxy"] = kwargs.pop("proxy")
|
|
46
|
+
return out
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class AgentPayClient(httpx.Client):
|
|
50
|
+
"""
|
|
51
|
+
HTTP client for autonomous AI agents with integrated financial firewall.
|
|
52
|
+
|
|
53
|
+
Inherits from httpx.Client: fully compatible with LangChain, LlamaIndex,
|
|
54
|
+
and standard Python HTTP toolchains. Set ``wallet_challenge_mode=True`` when
|
|
55
|
+
sending AgentPay Gateway API-key requests so the first request can receive
|
|
56
|
+
a price challenge before any wallet hold is placed.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
signer: ISigner,
|
|
62
|
+
policy: IPolicyEngine,
|
|
63
|
+
*,
|
|
64
|
+
transport: httpx.BaseTransport | None = None,
|
|
65
|
+
auto_retry_on_402: bool = True,
|
|
66
|
+
raise_on_policy_rejection: bool = False,
|
|
67
|
+
clock: Callable[[], int] | None = None,
|
|
68
|
+
unmetered_credential_policy: CredentialMode = "block",
|
|
69
|
+
credential_headers: tuple[str, ...] = DEFAULT_CREDENTIAL_HEADERS,
|
|
70
|
+
credential_value_prefixes: tuple[str, ...] = DEFAULT_CREDENTIAL_VALUE_PREFIXES,
|
|
71
|
+
estimate_cost: Callable[[httpx.Request], int] | None = None,
|
|
72
|
+
actual_cost: Callable[[httpx.Response], int | None] | None = None,
|
|
73
|
+
credential_ttl_seconds: int = 60,
|
|
74
|
+
wallet_challenge_mode: bool = False,
|
|
75
|
+
**kwargs: Any,
|
|
76
|
+
) -> None:
|
|
77
|
+
inner_kwargs = _inner_transport_kwargs(kwargs)
|
|
78
|
+
|
|
79
|
+
self.signer = signer
|
|
80
|
+
self.policy = policy
|
|
81
|
+
self.auto_retry_on_402 = auto_retry_on_402
|
|
82
|
+
self.raise_on_policy_rejection = raise_on_policy_rejection
|
|
83
|
+
self._clock = clock
|
|
84
|
+
self.unmetered_credential_policy = unmetered_credential_policy
|
|
85
|
+
# Single source of truth, reused by async_client() so both clients behave identically.
|
|
86
|
+
self._agentpay_options: dict[str, Any] = {
|
|
87
|
+
"auto_retry_on_402": auto_retry_on_402,
|
|
88
|
+
"raise_on_policy_rejection": raise_on_policy_rejection,
|
|
89
|
+
"clock": clock,
|
|
90
|
+
"unmetered_credential_policy": unmetered_credential_policy,
|
|
91
|
+
"credential_headers": credential_headers,
|
|
92
|
+
"credential_value_prefixes": credential_value_prefixes,
|
|
93
|
+
"estimate_cost": estimate_cost,
|
|
94
|
+
"actual_cost": actual_cost,
|
|
95
|
+
"credential_ttl_seconds": credential_ttl_seconds,
|
|
96
|
+
"wallet_challenge_mode": wallet_challenge_mode,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
inner = transport if transport is not None else httpx.HTTPTransport(**inner_kwargs)
|
|
100
|
+
agent_transport = AgentPayTransport(inner, signer, policy, **self._agentpay_options)
|
|
101
|
+
super().__init__(transport=agent_transport, **kwargs)
|
|
102
|
+
|
|
103
|
+
def async_client(
|
|
104
|
+
self,
|
|
105
|
+
*,
|
|
106
|
+
transport: httpx.AsyncBaseTransport | None = None,
|
|
107
|
+
**kwargs: Any,
|
|
108
|
+
) -> AsyncAgentPayClient:
|
|
109
|
+
"""Create an AsyncAgentPayClient sharing the same signer, policy and firewall options."""
|
|
110
|
+
options = {**self._agentpay_options, **{k: v for k, v in kwargs.items() if k in self._agentpay_options}}
|
|
111
|
+
client_kwargs = {k: v for k, v in kwargs.items() if k not in self._agentpay_options}
|
|
112
|
+
return AsyncAgentPayClient(
|
|
113
|
+
signer=self.signer,
|
|
114
|
+
policy=self.policy,
|
|
115
|
+
transport=transport,
|
|
116
|
+
**options,
|
|
117
|
+
**client_kwargs,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class AsyncAgentPayClient(httpx.AsyncClient):
|
|
122
|
+
"""
|
|
123
|
+
Asynchronous HTTP client for autonomous AI agents with integrated financial firewall.
|
|
124
|
+
|
|
125
|
+
Inherits from httpx.AsyncClient. Use ``wallet_challenge_mode=True`` for
|
|
126
|
+
AgentPay Gateway API-key requests that must be priced before wallet payment.
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
def __init__(
|
|
130
|
+
self,
|
|
131
|
+
signer: ISigner,
|
|
132
|
+
policy: IPolicyEngine,
|
|
133
|
+
*,
|
|
134
|
+
transport: httpx.AsyncBaseTransport | None = None,
|
|
135
|
+
auto_retry_on_402: bool = True,
|
|
136
|
+
raise_on_policy_rejection: bool = False,
|
|
137
|
+
clock: Callable[[], int] | None = None,
|
|
138
|
+
unmetered_credential_policy: CredentialMode = "block",
|
|
139
|
+
credential_headers: tuple[str, ...] = DEFAULT_CREDENTIAL_HEADERS,
|
|
140
|
+
credential_value_prefixes: tuple[str, ...] = DEFAULT_CREDENTIAL_VALUE_PREFIXES,
|
|
141
|
+
estimate_cost: Callable[[httpx.Request], int] | None = None,
|
|
142
|
+
actual_cost: Callable[[httpx.Response], int | None] | None = None,
|
|
143
|
+
credential_ttl_seconds: int = 60,
|
|
144
|
+
wallet_challenge_mode: bool = False,
|
|
145
|
+
**kwargs: Any,
|
|
146
|
+
) -> None:
|
|
147
|
+
inner_kwargs = _inner_transport_kwargs(kwargs)
|
|
148
|
+
|
|
149
|
+
self.signer = signer
|
|
150
|
+
self.policy = policy
|
|
151
|
+
self.auto_retry_on_402 = auto_retry_on_402
|
|
152
|
+
self.raise_on_policy_rejection = raise_on_policy_rejection
|
|
153
|
+
self._clock = clock
|
|
154
|
+
self.unmetered_credential_policy = unmetered_credential_policy
|
|
155
|
+
self._agentpay_options: dict[str, Any] = {
|
|
156
|
+
"auto_retry_on_402": auto_retry_on_402,
|
|
157
|
+
"raise_on_policy_rejection": raise_on_policy_rejection,
|
|
158
|
+
"clock": clock,
|
|
159
|
+
"unmetered_credential_policy": unmetered_credential_policy,
|
|
160
|
+
"credential_headers": credential_headers,
|
|
161
|
+
"credential_value_prefixes": credential_value_prefixes,
|
|
162
|
+
"estimate_cost": estimate_cost,
|
|
163
|
+
"actual_cost": actual_cost,
|
|
164
|
+
"credential_ttl_seconds": credential_ttl_seconds,
|
|
165
|
+
"wallet_challenge_mode": wallet_challenge_mode,
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
inner = transport if transport is not None else httpx.AsyncHTTPTransport(**inner_kwargs)
|
|
169
|
+
agent_transport = AsyncAgentPayTransport(inner, signer, policy, **self._agentpay_options)
|
|
170
|
+
super().__init__(transport=agent_transport, **kwargs)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
httpx transparent 402 interceptor transports for AgentPay.
|
|
3
3
|
|
|
4
4
|
Implements the autonomous financial lifecycle:
|
|
5
|
-
Intercept 402 -> Policy Evaluate & Reserve -> Sign
|
|
5
|
+
Intercept 402 -> Policy Evaluate & Reserve -> Sign or authorize -> Replay with PAYMENT-SIGNATURE -> Commit or Hold
|
|
6
6
|
|
|
7
7
|
Adheres strictly to CLAUDE.md safety rules:
|
|
8
8
|
- Fail-closed evaluation
|
|
@@ -21,11 +21,17 @@ answered 200 without any 402-round trip, which would bypass the local firewall.
|
|
|
21
21
|
"allow" legacy behaviour, firewall is bypassed (explicit opt-in only)
|
|
22
22
|
|
|
23
23
|
A single request can opt out with ``extensions={"agentpay_allow_unmetered": True}``.
|
|
24
|
+
|
|
25
|
+
For AgentPay Gateway virtual wallets, ``wallet_challenge_mode=True`` bypasses the
|
|
26
|
+
pre-request credential meter so the gateway can return a price challenge first;
|
|
27
|
+
the challenge is then evaluated locally before the authorized retry is sent.
|
|
24
28
|
"""
|
|
25
29
|
|
|
26
30
|
from __future__ import annotations
|
|
27
31
|
|
|
32
|
+
import hashlib
|
|
28
33
|
import logging
|
|
34
|
+
import secrets
|
|
29
35
|
import time
|
|
30
36
|
import uuid
|
|
31
37
|
from collections.abc import Callable
|
|
@@ -35,10 +41,11 @@ import httpx
|
|
|
35
41
|
|
|
36
42
|
from agentpay.client.interfaces import ISigner
|
|
37
43
|
from agentpay.core.codecs import (
|
|
44
|
+
encode_wallet_authorization,
|
|
38
45
|
extract_challenge_from_headers,
|
|
39
46
|
inject_authorization_to_headers,
|
|
40
47
|
)
|
|
41
|
-
from agentpay.core.models import PaymentAuthorization
|
|
48
|
+
from agentpay.core.models import PaymentAuthorization, WalletAuthorization
|
|
42
49
|
from agentpay.policy.errors import PolicyErrorCode, PolicyViolationError
|
|
43
50
|
from agentpay.policy.interfaces import IPolicyEngine
|
|
44
51
|
from agentpay.policy.metered import IMeteredPolicyEngine
|
|
@@ -117,6 +124,7 @@ class _AgentPayCore:
|
|
|
117
124
|
estimate_cost: Callable[[httpx.Request], int] | None,
|
|
118
125
|
actual_cost: Callable[[httpx.Response], int | None] | None,
|
|
119
126
|
credential_ttl_seconds: int,
|
|
127
|
+
wallet_challenge_mode: bool,
|
|
120
128
|
) -> None:
|
|
121
129
|
if unmetered_credential_policy not in ("block", "meter", "allow"):
|
|
122
130
|
raise ValueError(
|
|
@@ -137,6 +145,7 @@ class _AgentPayCore:
|
|
|
137
145
|
self._estimate_cost = estimate_cost
|
|
138
146
|
self._actual_cost = actual_cost
|
|
139
147
|
self._cred_ttl = credential_ttl_seconds
|
|
148
|
+
self._wallet_challenge_mode = wallet_challenge_mode
|
|
140
149
|
|
|
141
150
|
# -- credential-mode ---------------------------------------------------
|
|
142
151
|
|
|
@@ -154,6 +163,10 @@ class _AgentPayCore:
|
|
|
154
163
|
|
|
155
164
|
def _meter_begin(self, request: httpx.Request) -> str | None:
|
|
156
165
|
"""Runs BEFORE the request is sent. Returns an intent id if a metered reservation was taken."""
|
|
166
|
+
# This opt-in is only for AgentPay Gateway wallet routes: the first call must reach
|
|
167
|
+
# the gateway to obtain its price challenge, which is then policy-checked below.
|
|
168
|
+
if self._wallet_challenge_mode and self._has_credential(request):
|
|
169
|
+
return None
|
|
157
170
|
if request.extensions.get(ALLOW_UNMETERED_EXTENSION) or not self._has_credential(request):
|
|
158
171
|
return None
|
|
159
172
|
if self._cred_mode == "allow":
|
|
@@ -211,8 +224,20 @@ class _AgentPayCore:
|
|
|
211
224
|
return None # not a compliant x402 challenge
|
|
212
225
|
if challenge.is_expired(self._clock()):
|
|
213
226
|
return None
|
|
227
|
+
if challenge.scheme not in ("EIP-3009", "agentpay-wallet"):
|
|
228
|
+
raise PolicyViolationError(PolicyErrorCode.POLICY_SYSTEM_ERROR, f"unsupported payment scheme: {challenge.scheme}")
|
|
229
|
+
if challenge.scheme == "agentpay-wallet":
|
|
230
|
+
expected_binding = self._request_binding(request)
|
|
231
|
+
if (challenge.extensions or {}).get("requestBinding") != expected_binding:
|
|
232
|
+
raise PolicyViolationError(PolicyErrorCode.POLICY_SYSTEM_ERROR, "wallet challenge requestBinding mismatch")
|
|
233
|
+
if not (challenge.extensions or {}).get("routeId"):
|
|
234
|
+
raise PolicyViolationError(PolicyErrorCode.POLICY_SYSTEM_ERROR, "wallet challenge missing routeId")
|
|
235
|
+
raw_path, _, _ = request.url.raw_path.partition(b"?")
|
|
236
|
+
if challenge.resource_uri != (raw_path.decode("ascii") or "/"):
|
|
237
|
+
raise PolicyViolationError(PolicyErrorCode.POLICY_SYSTEM_ERROR, "wallet challenge resourceUri mismatch")
|
|
214
238
|
|
|
215
239
|
payment_request = challenge.to_payment_request(domain=request.url.host)
|
|
240
|
+
# SECURITY-REVIEW: reserve the exact seller challenge amount before generating wallet authorization.
|
|
216
241
|
decision = self._policy.reserve(
|
|
217
242
|
intent_id=payment_request.intent_id,
|
|
218
243
|
amount=payment_request.amount,
|
|
@@ -231,19 +256,58 @@ class _AgentPayCore:
|
|
|
231
256
|
raise PolicyViolationError(PolicyErrorCode.POLICY_SYSTEM_ERROR, "approved decision without reservation")
|
|
232
257
|
return challenge, decision.reservation
|
|
233
258
|
|
|
259
|
+
@staticmethod
|
|
260
|
+
def _request_binding(request: httpx.Request) -> str:
|
|
261
|
+
"""Match the Go Gateway's method/path/query/body SHA-256 canonicalization."""
|
|
262
|
+
raw_path, marker, raw_query = request.url.raw_path.partition(b"?")
|
|
263
|
+
path = raw_path.decode("ascii") or "/"
|
|
264
|
+
query = raw_query.decode("ascii") if marker else ""
|
|
265
|
+
body_digest = hashlib.sha256(request.read()).hexdigest()
|
|
266
|
+
canonical = f"{request.method.upper()}\n{path}\n{query}\n{body_digest}".encode("utf-8")
|
|
267
|
+
return "sha256:" + hashlib.sha256(canonical).hexdigest()
|
|
268
|
+
|
|
234
269
|
def _sign_and_clone(self, request: httpx.Request, challenge: Any, reservation: Any) -> httpx.Request:
|
|
235
270
|
# Everything here happens BEFORE transmission, so releasing on failure is safe.
|
|
236
271
|
try:
|
|
272
|
+
if challenge.scheme == "agentpay-wallet":
|
|
273
|
+
# SECURITY-REVIEW: the challenge-bound reservation is the buyer-side approval gate.
|
|
274
|
+
if reservation.intent_id != challenge.challenge_id or reservation.amount != challenge.amount_base_units:
|
|
275
|
+
raise PolicyViolationError(PolicyErrorCode.POLICY_SYSTEM_ERROR, "wallet reservation does not match challenge")
|
|
276
|
+
if reservation.expires_at < challenge.valid_before:
|
|
277
|
+
raise PolicyViolationError(PolicyErrorCode.POLICY_SYSTEM_ERROR, "wallet reservation expires before challenge")
|
|
278
|
+
request_binding = (challenge.extensions or {}).get("requestBinding")
|
|
279
|
+
if not request_binding:
|
|
280
|
+
raise PolicyViolationError(PolicyErrorCode.POLICY_SYSTEM_ERROR, "wallet challenge missing requestBinding")
|
|
281
|
+
wallet_auth = WalletAuthorization(
|
|
282
|
+
challengeId=challenge.challenge_id,
|
|
283
|
+
nonce="0x" + secrets.token_bytes(32).hex(),
|
|
284
|
+
validAfter=challenge.valid_after,
|
|
285
|
+
validBefore=challenge.valid_before,
|
|
286
|
+
amount=challenge.amount,
|
|
287
|
+
resourceUri=challenge.resource_uri,
|
|
288
|
+
requestBinding=request_binding,
|
|
289
|
+
)
|
|
290
|
+
headers = httpx.Headers(request.headers)
|
|
291
|
+
headers["PAYMENT-SIGNATURE"] = encode_wallet_authorization(wallet_auth)
|
|
292
|
+
content = request.read()
|
|
293
|
+
return httpx.Request(
|
|
294
|
+
method=request.method, url=request.url, headers=headers,
|
|
295
|
+
content=content, extensions=dict(request.extensions),
|
|
296
|
+
)
|
|
237
297
|
authorization = self._signer.sign_payment_authorization(challenge, reservation)
|
|
238
298
|
return _clone_request_with_authorization(request, authorization)
|
|
239
299
|
except Exception:
|
|
240
300
|
self._policy.release(reservation.intent_id)
|
|
241
301
|
raise
|
|
242
302
|
|
|
243
|
-
def _settle_x402(self, intent_id: str, retry_response: httpx.Response) -> None:
|
|
303
|
+
def _settle_x402(self, intent_id: str, retry_response: httpx.Response, scheme: str) -> None:
|
|
244
304
|
code = retry_response.status_code
|
|
305
|
+
# SECURITY-REVIEW: only a definitive wallet 4xx releases; ambiguous and 5xx outcomes stay reserved.
|
|
245
306
|
if 200 <= code < 300:
|
|
246
307
|
self._policy.commit(intent_id)
|
|
308
|
+
elif scheme == "agentpay-wallet" and 400 <= code < 500:
|
|
309
|
+
# Wallet authorization has no chain signature; a definitive gateway rejection means no hold was dispatched.
|
|
310
|
+
self._policy.release(intent_id)
|
|
247
311
|
elif code >= 500:
|
|
248
312
|
# Signature is on the wire: keep RESERVED (no auto release) and feed the breaker.
|
|
249
313
|
_safe_record_failure(self._policy, f"upstream {code} after payment")
|
|
@@ -259,8 +323,8 @@ class AgentPayTransport(_AgentPayCore, httpx.BaseTransport):
|
|
|
259
323
|
Synchronous httpx transport interceptor for AgentPay.
|
|
260
324
|
|
|
261
325
|
Transparently catches HTTP 402 Payment Required responses, evaluates the
|
|
262
|
-
challenge against the local policy firewall, signs
|
|
263
|
-
|
|
326
|
+
challenge against the local policy firewall, then either signs EIP-3009 or
|
|
327
|
+
creates an AgentPay wallet authorization before replaying the request.
|
|
264
328
|
"""
|
|
265
329
|
|
|
266
330
|
def __init__(
|
|
@@ -278,6 +342,7 @@ class AgentPayTransport(_AgentPayCore, httpx.BaseTransport):
|
|
|
278
342
|
estimate_cost: Callable[[httpx.Request], int] | None = None,
|
|
279
343
|
actual_cost: Callable[[httpx.Response], int | None] | None = None,
|
|
280
344
|
credential_ttl_seconds: int = 60,
|
|
345
|
+
wallet_challenge_mode: bool = False,
|
|
281
346
|
) -> None:
|
|
282
347
|
httpx.BaseTransport.__init__(self)
|
|
283
348
|
_AgentPayCore.__init__(
|
|
@@ -293,6 +358,7 @@ class AgentPayTransport(_AgentPayCore, httpx.BaseTransport):
|
|
|
293
358
|
estimate_cost=estimate_cost,
|
|
294
359
|
actual_cost=actual_cost,
|
|
295
360
|
credential_ttl_seconds=credential_ttl_seconds,
|
|
361
|
+
wallet_challenge_mode=wallet_challenge_mode,
|
|
296
362
|
)
|
|
297
363
|
self._inner_transport = inner_transport
|
|
298
364
|
|
|
@@ -311,6 +377,13 @@ class AgentPayTransport(_AgentPayCore, httpx.BaseTransport):
|
|
|
311
377
|
if meter_id is not None:
|
|
312
378
|
self._meter_end(meter_id, response)
|
|
313
379
|
|
|
380
|
+
if self._wallet_challenge_mode and self._has_credential(request) and 200 <= response.status_code < 300:
|
|
381
|
+
response.close()
|
|
382
|
+
raise PolicyViolationError(
|
|
383
|
+
PolicyErrorCode.UNMETERED_CREDENTIAL,
|
|
384
|
+
"Wallet challenge mode expected a 402 price challenge; the gateway served the credential request directly.",
|
|
385
|
+
)
|
|
386
|
+
|
|
314
387
|
if response.status_code != 402 or not self._auto_retry:
|
|
315
388
|
return response
|
|
316
389
|
|
|
@@ -329,7 +402,7 @@ class AgentPayTransport(_AgentPayCore, httpx.BaseTransport):
|
|
|
329
402
|
_safe_record_failure(self._policy, "transport error after payment signature sent")
|
|
330
403
|
raise
|
|
331
404
|
|
|
332
|
-
self._settle_x402(reservation.intent_id, retry_response)
|
|
405
|
+
self._settle_x402(reservation.intent_id, retry_response, challenge.scheme)
|
|
333
406
|
return retry_response
|
|
334
407
|
|
|
335
408
|
|
|
@@ -356,6 +429,7 @@ class AsyncAgentPayTransport(_AgentPayCore, httpx.AsyncBaseTransport):
|
|
|
356
429
|
estimate_cost: Callable[[httpx.Request], int] | None = None,
|
|
357
430
|
actual_cost: Callable[[httpx.Response], int | None] | None = None,
|
|
358
431
|
credential_ttl_seconds: int = 60,
|
|
432
|
+
wallet_challenge_mode: bool = False,
|
|
359
433
|
) -> None:
|
|
360
434
|
httpx.AsyncBaseTransport.__init__(self)
|
|
361
435
|
_AgentPayCore.__init__(
|
|
@@ -371,6 +445,7 @@ class AsyncAgentPayTransport(_AgentPayCore, httpx.AsyncBaseTransport):
|
|
|
371
445
|
estimate_cost=estimate_cost,
|
|
372
446
|
actual_cost=actual_cost,
|
|
373
447
|
credential_ttl_seconds=credential_ttl_seconds,
|
|
448
|
+
wallet_challenge_mode=wallet_challenge_mode,
|
|
374
449
|
)
|
|
375
450
|
self._inner_transport = inner_transport
|
|
376
451
|
|
|
@@ -388,6 +463,13 @@ class AsyncAgentPayTransport(_AgentPayCore, httpx.AsyncBaseTransport):
|
|
|
388
463
|
if meter_id is not None:
|
|
389
464
|
self._meter_end(meter_id, response)
|
|
390
465
|
|
|
466
|
+
if self._wallet_challenge_mode and self._has_credential(request) and 200 <= response.status_code < 300:
|
|
467
|
+
await response.aclose()
|
|
468
|
+
raise PolicyViolationError(
|
|
469
|
+
PolicyErrorCode.UNMETERED_CREDENTIAL,
|
|
470
|
+
"Wallet challenge mode expected a 402 price challenge; the gateway served the credential request directly.",
|
|
471
|
+
)
|
|
472
|
+
|
|
391
473
|
if response.status_code != 402 or not self._auto_retry:
|
|
392
474
|
return response
|
|
393
475
|
|
|
@@ -405,5 +487,5 @@ class AsyncAgentPayTransport(_AgentPayCore, httpx.AsyncBaseTransport):
|
|
|
405
487
|
_safe_record_failure(self._policy, "transport error after payment signature sent")
|
|
406
488
|
raise
|
|
407
489
|
|
|
408
|
-
self._settle_x402(reservation.intent_id, retry_response)
|
|
490
|
+
self._settle_x402(reservation.intent_id, retry_response, challenge.scheme)
|
|
409
491
|
return retry_response
|
{open_agentpay-0.2.1/src/agentpay/protocol → open_agentpay-0.2.2/src/agentpay/core}/codecs.py
RENAMED
|
@@ -23,7 +23,7 @@ from agentpay.core.errors import (
|
|
|
23
23
|
X402ErrorCode,
|
|
24
24
|
X402ValidationError,
|
|
25
25
|
)
|
|
26
|
-
from agentpay.core.models import PaymentAuthorization, PaymentChallenge
|
|
26
|
+
from agentpay.core.models import PaymentAuthorization, PaymentChallenge, WalletAuthorization
|
|
27
27
|
|
|
28
28
|
# Standard HTTP Header Constants (RFC-compliant uppercase default)
|
|
29
29
|
HEADER_PAYMENT_REQUIRED = "PAYMENT-REQUIRED"
|
|
@@ -176,6 +176,15 @@ def encode_authorization(authorization: PaymentAuthorization, *, base64_url: boo
|
|
|
176
176
|
) from exc
|
|
177
177
|
|
|
178
178
|
|
|
179
|
+
def encode_wallet_authorization(authorization: WalletAuthorization, *, base64_url: bool = True) -> str:
|
|
180
|
+
"""Encode AgentPay's non-EIP-3009 wallet authorization in PAYMENT-SIGNATURE."""
|
|
181
|
+
try:
|
|
182
|
+
payload = authorization.model_dump_json(by_alias=True, exclude_none=True)
|
|
183
|
+
return base64url_encode(payload.encode("utf-8")) if base64_url else payload
|
|
184
|
+
except Exception as exc: # noqa: BLE001
|
|
185
|
+
raise X402EncodeError("Failed to encode wallet authorization", code=X402ErrorCode.MALFORMED_HEADER) from exc
|
|
186
|
+
|
|
187
|
+
|
|
179
188
|
def decode_authorization(header_value: str) -> PaymentAuthorization:
|
|
180
189
|
"""
|
|
181
190
|
Decode an HTTP PAYMENT-SIGNATURE header value (Base64URL or raw JSON) into a PaymentAuthorization model.
|
{open_agentpay-0.2.1/src/agentpay/protocol → open_agentpay-0.2.2/src/agentpay/core}/models.py
RENAMED
|
@@ -146,21 +146,22 @@ class PaymentChallenge(BaseModel):
|
|
|
146
146
|
Corresponds to the PAYMENT-REQUIRED header.
|
|
147
147
|
"""
|
|
148
148
|
|
|
149
|
-
model_config = ConfigDict(frozen=True, populate_by_name=True)
|
|
149
|
+
model_config = ConfigDict(frozen=True, populate_by_name=True, extra="allow")
|
|
150
150
|
|
|
151
151
|
version: str = Field(default="2.0", description="Protocol version, fixed to 2.0")
|
|
152
152
|
challenge_id: str = Field(..., alias="challengeId", description="Globally unique challenge ID")
|
|
153
|
-
|
|
154
|
-
|
|
153
|
+
scheme: str = Field(default="EIP-3009", description="Payment authorization scheme")
|
|
154
|
+
network: str | None = Field(default=None, description="CAIP-2 network or AgentPay internal ledger")
|
|
155
|
+
asset: str | None = Field(default=None, description="Payment asset identifier")
|
|
155
156
|
asset_symbol: str | None = Field(default=None, alias="assetSymbol", description="Token symbol")
|
|
156
157
|
decimals: int = Field(default=6, description="Token decimals")
|
|
157
158
|
amount: str = Field(..., description="Payment amount in integer base units as string")
|
|
158
|
-
payee: str = Field(
|
|
159
|
-
scheme: str = Field(default="EIP-3009", description="Payment authorization scheme")
|
|
159
|
+
payee: str | None = Field(default=None, description="Chain recipient or AgentPay route resource")
|
|
160
160
|
valid_after: int = Field(..., alias="validAfter", description="Unix timestamp valid after (seconds)")
|
|
161
161
|
valid_before: int = Field(..., alias="validBefore", description="Unix timestamp valid before (seconds)")
|
|
162
|
-
facilitator_url: str = Field(
|
|
162
|
+
facilitator_url: str | None = Field(default=None, alias="facilitatorUrl", description="Facilitator clearing endpoint URL")
|
|
163
163
|
resource_uri: str | None = Field(default=None, alias="resourceUri", description="Protected resource path")
|
|
164
|
+
extensions: dict[str, Any] | None = None
|
|
164
165
|
|
|
165
166
|
def __init__(self, /, **data: Any) -> None:
|
|
166
167
|
try:
|
|
@@ -217,17 +218,29 @@ class PaymentChallenge(BaseModel):
|
|
|
217
218
|
|
|
218
219
|
@field_validator("network")
|
|
219
220
|
@classmethod
|
|
220
|
-
def _validate_network(cls, v: str) -> str:
|
|
221
|
+
def _validate_network(cls, v: str | None, info: Any) -> str | None:
|
|
222
|
+
if info.data.get("scheme") == "agentpay-wallet" and v == "agentpay:internal-ledger":
|
|
223
|
+
return v
|
|
224
|
+
if v is None:
|
|
225
|
+
raise X402ValidationError("network is required", code=X402ErrorCode.MISSING_REQUIRED_FIELD)
|
|
221
226
|
return _validate_caip2(v)
|
|
222
227
|
|
|
223
228
|
@field_validator("asset")
|
|
224
229
|
@classmethod
|
|
225
|
-
def _validate_asset(cls, v: str) -> str:
|
|
230
|
+
def _validate_asset(cls, v: str | None, info: Any) -> str | None:
|
|
231
|
+
if info.data.get("scheme") == "agentpay-wallet" and v == "USD":
|
|
232
|
+
return v
|
|
233
|
+
if v is None:
|
|
234
|
+
raise X402ValidationError("asset is required", code=X402ErrorCode.MISSING_REQUIRED_FIELD)
|
|
226
235
|
return _validate_evm_address(v, "asset")
|
|
227
236
|
|
|
228
237
|
@field_validator("payee")
|
|
229
238
|
@classmethod
|
|
230
|
-
def _validate_payee(cls, v: str) -> str:
|
|
239
|
+
def _validate_payee(cls, v: str | None, info: Any) -> str | None:
|
|
240
|
+
if info.data.get("scheme") == "agentpay-wallet" and v and v.startswith("agentpay:route:"):
|
|
241
|
+
return v
|
|
242
|
+
if v is None:
|
|
243
|
+
raise X402ValidationError("payee is required", code=X402ErrorCode.MISSING_REQUIRED_FIELD)
|
|
231
244
|
return _validate_evm_address(v, "payee")
|
|
232
245
|
|
|
233
246
|
@field_validator("decimals")
|
|
@@ -315,6 +328,17 @@ class PaymentChallenge(BaseModel):
|
|
|
315
328
|
)
|
|
316
329
|
return self
|
|
317
330
|
|
|
331
|
+
@model_validator(mode="after")
|
|
332
|
+
def _validate_scheme_fields(self) -> PaymentChallenge:
|
|
333
|
+
if self.scheme == "agentpay-wallet":
|
|
334
|
+
if self.network != "agentpay:internal-ledger" or self.asset != "USD" or not self.resource_uri:
|
|
335
|
+
raise X402ValidationError("invalid agentpay-wallet challenge fields", code=X402ErrorCode.INVALID_SCHEME)
|
|
336
|
+
if self.facilitator_url:
|
|
337
|
+
raise X402ValidationError("agentpay-wallet challenge must not name an EIP-3009 facilitator", code=X402ErrorCode.INVALID_SCHEME)
|
|
338
|
+
elif not self.network or not self.asset or not self.payee or not self.facilitator_url:
|
|
339
|
+
raise X402ValidationError("EIP-3009 challenge requires network, asset, payee, and facilitatorUrl", code=X402ErrorCode.MISSING_REQUIRED_FIELD)
|
|
340
|
+
return self
|
|
341
|
+
|
|
318
342
|
@property
|
|
319
343
|
def amount_base_units(self) -> int:
|
|
320
344
|
"""Payment amount parsed into integer base units."""
|
|
@@ -341,9 +365,9 @@ class PaymentChallenge(BaseModel):
|
|
|
341
365
|
return PaymentRequest(
|
|
342
366
|
intent_id=intent_id or self.challenge_id,
|
|
343
367
|
amount=self.amount_base_units,
|
|
344
|
-
payee=self.payee,
|
|
345
|
-
asset=self.asset,
|
|
346
|
-
network=self.network,
|
|
368
|
+
payee=self.payee or "agentpay:route:unknown",
|
|
369
|
+
asset=self.asset or "USD",
|
|
370
|
+
network=self.network or "agentpay:internal-ledger",
|
|
347
371
|
domain=domain,
|
|
348
372
|
ttl_seconds=ttl_seconds,
|
|
349
373
|
)
|
|
@@ -460,3 +484,41 @@ class PaymentAuthorization(BaseModel):
|
|
|
460
484
|
code=X402ErrorCode.INVALID_TIME_WINDOW,
|
|
461
485
|
)
|
|
462
486
|
return self
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
class WalletAuthorization(BaseModel):
|
|
490
|
+
"""AgentPay's API-key-authenticated wallet approval payload (not a cryptographic signature)."""
|
|
491
|
+
|
|
492
|
+
model_config = ConfigDict(frozen=True, populate_by_name=True)
|
|
493
|
+
|
|
494
|
+
version: str = "2.0"
|
|
495
|
+
challenge_id: str = Field(..., alias="challengeId")
|
|
496
|
+
scheme: str = "agentpay-wallet"
|
|
497
|
+
nonce: str
|
|
498
|
+
valid_after: int = Field(..., alias="validAfter")
|
|
499
|
+
valid_before: int = Field(..., alias="validBefore")
|
|
500
|
+
amount: str
|
|
501
|
+
resource_uri: str = Field(..., alias="resourceUri")
|
|
502
|
+
request_binding: str = Field(..., alias="requestBinding")
|
|
503
|
+
|
|
504
|
+
@field_validator("nonce")
|
|
505
|
+
@classmethod
|
|
506
|
+
def _validate_wallet_nonce(cls, value: str) -> str:
|
|
507
|
+
if not re.fullmatch(r"0x[0-9a-f]{64}", value):
|
|
508
|
+
raise X402ValidationError("wallet nonce must be 32 random bytes encoded as lowercase hex", code=X402ErrorCode.INVALID_NONCE)
|
|
509
|
+
return value
|
|
510
|
+
|
|
511
|
+
@field_validator("amount")
|
|
512
|
+
@classmethod
|
|
513
|
+
def _validate_wallet_amount(cls, value: str) -> str:
|
|
514
|
+
if not value.isdecimal() or int(value) <= 0 or int(value) > SQLITE_MAX_INT:
|
|
515
|
+
raise X402ValidationError("wallet amount must be a positive int64 decimal string", code=X402ErrorCode.INVALID_AMOUNT_FORMAT)
|
|
516
|
+
return value
|
|
517
|
+
|
|
518
|
+
@model_validator(mode="after")
|
|
519
|
+
def _validate_wallet_fields(self) -> WalletAuthorization:
|
|
520
|
+
if self.version != "2.0" or self.scheme != "agentpay-wallet" or self.valid_before <= self.valid_after:
|
|
521
|
+
raise X402ValidationError("invalid agentpay-wallet authorization fields", code=X402ErrorCode.INVALID_SCHEME)
|
|
522
|
+
if not self.challenge_id or not self.resource_uri or not re.fullmatch(r"sha256:[0-9a-f]{64}", self.request_binding):
|
|
523
|
+
raise X402ValidationError("wallet authorization is missing challenge binding fields", code=X402ErrorCode.MISSING_REQUIRED_FIELD)
|
|
524
|
+
return self
|
|
@@ -199,7 +199,13 @@ class PolicyConfig(BaseModel):
|
|
|
199
199
|
for addr in v:
|
|
200
200
|
if not isinstance(addr, str):
|
|
201
201
|
raise TypeError(f"whitelist_payees elements must be strings, got {type(addr).__name__}")
|
|
202
|
-
|
|
202
|
+
normalized: list[str] = []
|
|
203
|
+
for addr in v:
|
|
204
|
+
if addr.startswith("agentpay:route:") and len(addr) > len("agentpay:route:"):
|
|
205
|
+
normalized.append(addr)
|
|
206
|
+
else:
|
|
207
|
+
normalized.append(_validate_evm_address(addr, "whitelist_payees"))
|
|
208
|
+
return frozenset(normalized)
|
|
203
209
|
|
|
204
210
|
@field_validator("whitelist_assets", mode="before")
|
|
205
211
|
@classmethod
|
|
@@ -211,7 +217,10 @@ class PolicyConfig(BaseModel):
|
|
|
211
217
|
for addr in v:
|
|
212
218
|
if not isinstance(addr, str):
|
|
213
219
|
raise TypeError(f"whitelist_assets elements must be strings, got {type(addr).__name__}")
|
|
214
|
-
|
|
220
|
+
normalized: list[str] = []
|
|
221
|
+
for addr in v:
|
|
222
|
+
normalized.append("USD" if addr == "USD" else _validate_evm_address(addr, "whitelist_assets"))
|
|
223
|
+
return frozenset(normalized)
|
|
215
224
|
|
|
216
225
|
@field_validator("whitelist_networks", mode="before")
|
|
217
226
|
@classmethod
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
AgentPayClient: Drop-in httpx.Client with automatic 402 payment negotiation.
|
|
3
|
-
|
|
4
|
-
Wraps the financial firewall and EIP-712 cryptographic signer to automatically
|
|
5
|
-
satisfy HTTP 402 Payment Required challenges.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from __future__ import annotations
|
|
9
|
-
|
|
10
|
-
from collections.abc import Callable
|
|
11
|
-
from typing import Any
|
|
12
|
-
|
|
13
|
-
import httpx
|
|
14
|
-
|
|
15
|
-
from agentpay.client.interfaces import ISigner
|
|
16
|
-
from agentpay.client.transport import AgentPayTransport, AsyncAgentPayTransport
|
|
17
|
-
from agentpay.policy.interfaces import IPolicyEngine
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class AgentPayClient(httpx.Client):
|
|
21
|
-
"""
|
|
22
|
-
HTTP client for autonomous AI agents with integrated financial firewall.
|
|
23
|
-
|
|
24
|
-
Inherits from httpx.Client: fully compatible with LangChain, LlamaIndex,
|
|
25
|
-
and standard Python HTTP toolchains.
|
|
26
|
-
"""
|
|
27
|
-
|
|
28
|
-
def __init__(
|
|
29
|
-
self,
|
|
30
|
-
signer: ISigner,
|
|
31
|
-
policy: IPolicyEngine,
|
|
32
|
-
*,
|
|
33
|
-
transport: httpx.BaseTransport | None = None,
|
|
34
|
-
auto_retry_on_402: bool = True,
|
|
35
|
-
raise_on_policy_rejection: bool = False,
|
|
36
|
-
clock: Callable[[], int] | None = None,
|
|
37
|
-
**kwargs: Any,
|
|
38
|
-
) -> None:
|
|
39
|
-
self.signer = signer
|
|
40
|
-
self.policy = policy
|
|
41
|
-
self.auto_retry_on_402 = auto_retry_on_402
|
|
42
|
-
self.raise_on_policy_rejection = raise_on_policy_rejection
|
|
43
|
-
self._clock = clock
|
|
44
|
-
|
|
45
|
-
inner = transport or httpx.HTTPTransport()
|
|
46
|
-
agent_transport = AgentPayTransport(
|
|
47
|
-
inner_transport=inner,
|
|
48
|
-
signer=signer,
|
|
49
|
-
policy=policy,
|
|
50
|
-
auto_retry_on_402=auto_retry_on_402,
|
|
51
|
-
raise_on_policy_rejection=raise_on_policy_rejection,
|
|
52
|
-
clock=clock,
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
super().__init__(transport=agent_transport, **kwargs)
|
|
56
|
-
|
|
57
|
-
def async_client(
|
|
58
|
-
self,
|
|
59
|
-
*,
|
|
60
|
-
transport: httpx.AsyncBaseTransport | None = None,
|
|
61
|
-
**kwargs: Any,
|
|
62
|
-
) -> AsyncAgentPayClient:
|
|
63
|
-
"""Create an asynchronous AsyncAgentPayClient sharing the same signer and policy."""
|
|
64
|
-
return AsyncAgentPayClient(
|
|
65
|
-
signer=self.signer,
|
|
66
|
-
policy=self.policy,
|
|
67
|
-
transport=transport,
|
|
68
|
-
auto_retry_on_402=self.auto_retry_on_402,
|
|
69
|
-
raise_on_policy_rejection=self.raise_on_policy_rejection,
|
|
70
|
-
clock=self._clock,
|
|
71
|
-
**kwargs,
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
class AsyncAgentPayClient(httpx.AsyncClient):
|
|
76
|
-
"""
|
|
77
|
-
Asynchronous HTTP client for autonomous AI agents with integrated financial firewall.
|
|
78
|
-
|
|
79
|
-
Inherits from httpx.AsyncClient.
|
|
80
|
-
"""
|
|
81
|
-
|
|
82
|
-
def __init__(
|
|
83
|
-
self,
|
|
84
|
-
signer: ISigner,
|
|
85
|
-
policy: IPolicyEngine,
|
|
86
|
-
*,
|
|
87
|
-
transport: httpx.AsyncBaseTransport | None = None,
|
|
88
|
-
auto_retry_on_402: bool = True,
|
|
89
|
-
raise_on_policy_rejection: bool = False,
|
|
90
|
-
clock: Callable[[], int] | None = None,
|
|
91
|
-
**kwargs: Any,
|
|
92
|
-
) -> None:
|
|
93
|
-
self.signer = signer
|
|
94
|
-
self.policy = policy
|
|
95
|
-
self.auto_retry_on_402 = auto_retry_on_402
|
|
96
|
-
self.raise_on_policy_rejection = raise_on_policy_rejection
|
|
97
|
-
self._clock = clock
|
|
98
|
-
|
|
99
|
-
inner = transport or httpx.AsyncHTTPTransport()
|
|
100
|
-
agent_transport = AsyncAgentPayTransport(
|
|
101
|
-
inner_transport=inner,
|
|
102
|
-
signer=signer,
|
|
103
|
-
policy=policy,
|
|
104
|
-
auto_retry_on_402=auto_retry_on_402,
|
|
105
|
-
raise_on_policy_rejection=raise_on_policy_rejection,
|
|
106
|
-
clock=clock,
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
super().__init__(transport=agent_transport, **kwargs)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{open_agentpay-0.2.1/src/agentpay/core → open_agentpay-0.2.2/src/agentpay/protocol}/codecs.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{open_agentpay-0.2.1/src/agentpay/core → open_agentpay-0.2.2/src/agentpay/protocol}/models.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|