agentpayments-python 0.1.1__tar.gz → 0.1.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.
Files changed (26) hide show
  1. {agentpayments_python-0.1.1 → agentpayments_python-0.1.2}/PKG-INFO +1 -1
  2. {agentpayments_python-0.1.1 → agentpayments_python-0.1.2}/agentpayments_python/detection.py +11 -3
  3. {agentpayments_python-0.1.1 → agentpayments_python-0.1.2}/agentpayments_python.egg-info/PKG-INFO +1 -1
  4. {agentpayments_python-0.1.1 → agentpayments_python-0.1.2}/pyproject.toml +1 -1
  5. {agentpayments_python-0.1.1 → agentpayments_python-0.1.2}/tests/test_core.py +19 -0
  6. {agentpayments_python-0.1.1 → agentpayments_python-0.1.2}/LICENSE +0 -0
  7. {agentpayments_python-0.1.1 → agentpayments_python-0.1.2}/README.md +0 -0
  8. {agentpayments_python-0.1.1 → agentpayments_python-0.1.2}/agentpayments_python/__init__.py +0 -0
  9. {agentpayments_python-0.1.1 → agentpayments_python-0.1.2}/agentpayments_python/challenge.py +0 -0
  10. {agentpayments_python-0.1.1 → agentpayments_python-0.1.2}/agentpayments_python/constants.json +0 -0
  11. {agentpayments_python-0.1.1 → agentpayments_python-0.1.2}/agentpayments_python/cookies.py +0 -0
  12. {agentpayments_python-0.1.1 → agentpayments_python-0.1.2}/agentpayments_python/crawler.py +0 -0
  13. {agentpayments_python-0.1.1 → agentpayments_python-0.1.2}/agentpayments_python/crypto.py +0 -0
  14. {agentpayments_python-0.1.1 → agentpayments_python-0.1.2}/agentpayments_python/django_adapter.py +0 -0
  15. {agentpayments_python-0.1.1 → agentpayments_python-0.1.2}/agentpayments_python/fastapi_adapter.py +0 -0
  16. {agentpayments_python-0.1.1 → agentpayments_python-0.1.2}/agentpayments_python/flask_adapter.py +0 -0
  17. {agentpayments_python-0.1.1 → agentpayments_python-0.1.2}/agentpayments_python/grant_store.py +0 -0
  18. {agentpayments_python-0.1.1 → agentpayments_python-0.1.2}/agentpayments_python/platform_client.py +0 -0
  19. {agentpayments_python-0.1.1 → agentpayments_python-0.1.2}/agentpayments_python/ratelimit.py +0 -0
  20. {agentpayments_python-0.1.1 → agentpayments_python-0.1.2}/agentpayments_python/solana.py +0 -0
  21. {agentpayments_python-0.1.1 → agentpayments_python-0.1.2}/agentpayments_python/x402.py +0 -0
  22. {agentpayments_python-0.1.1 → agentpayments_python-0.1.2}/agentpayments_python.egg-info/SOURCES.txt +0 -0
  23. {agentpayments_python-0.1.1 → agentpayments_python-0.1.2}/agentpayments_python.egg-info/dependency_links.txt +0 -0
  24. {agentpayments_python-0.1.1 → agentpayments_python-0.1.2}/agentpayments_python.egg-info/requires.txt +0 -0
  25. {agentpayments_python-0.1.1 → agentpayments_python-0.1.2}/agentpayments_python.egg-info/top_level.txt +0 -0
  26. {agentpayments_python-0.1.1 → agentpayments_python-0.1.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentpayments-python
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: AgentPayments gate for Python web frameworks — charge AI agents USDC on Solana before they can access your API
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/adambrzosko/AgentPayments
@@ -18,10 +18,18 @@ _BOT_UA_RE = _re.compile(r"bot|crawl|spider|slurp|mediapartners|adsbot", _re.IGN
18
18
 
19
19
 
20
20
  def is_browser_from_headers(headers: dict) -> bool:
21
- # Primary signal: Fetch metadata headers (Chrome 76+, Firefox 90+).
22
- if headers.get("sec-fetch-mode") or headers.get("sec-fetch-dest"):
21
+ # Primary signal: Fetch metadata headers, but only the values set exclusively for
22
+ # top-level navigations. These values are not reachable through the fetch()/XHR
23
+ # APIs, so this doesn't just check for *presence* of the header: Node's built-in
24
+ # fetch() (undici) unconditionally sends `sec-fetch-mode: cors` on every outgoing
25
+ # request (not overridable, it's a forbidden header), which previously
26
+ # misclassified fetch()-based agents as browsers and served them the HTML
27
+ # challenge instead of the 402 JSON they need to read to pay.
28
+ if headers.get("sec-fetch-mode") == "navigate" or headers.get("sec-fetch-dest") == "document":
23
29
  return True
24
- # Fallback: UA heuristic for older browsers that don't send Sec-Fetch-*.
30
+ # Fallback: UA heuristic for older browsers that don't send Sec-Fetch-*, and for
31
+ # real in-page browser fetch()/XHR calls (Sec-Fetch-Mode: cors) which still carry
32
+ # a genuine browser User-Agent.
25
33
  ua = headers.get("user-agent") or headers.get("User-Agent") or ""
26
34
  if ua and not _BOT_UA_RE.search(ua) and _BROWSER_UA_RE.search(ua):
27
35
  return True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentpayments-python
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: AgentPayments gate for Python web frameworks — charge AI agents USDC on Solana before they can access your API
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/adambrzosko/AgentPayments
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "agentpayments-python"
7
- version = "0.1.1"
7
+ version = "0.1.2"
8
8
  description = "AgentPayments gate for Python web frameworks — charge AI agents USDC on Solana before they can access your API"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -340,6 +340,25 @@ class TestBrowserDetection:
340
340
  def test_no_ua_not_browser(self):
341
341
  assert not is_browser_from_headers({"accept": "text/html"})
342
342
 
343
+ def test_real_browser_navigation_is_browser(self):
344
+ # Shape of a real top-level browser navigation: Sec-Fetch-Mode: navigate and
345
+ # Sec-Fetch-Dest: document alongside a genuine browser UA.
346
+ assert is_browser_from_headers({
347
+ "sec-fetch-mode": "navigate",
348
+ "sec-fetch-dest": "document",
349
+ "user-agent": self.CHROME_UA,
350
+ })
351
+
352
+ def test_fetch_based_agent_not_browser(self):
353
+ # Node's built-in fetch() (undici) unconditionally sends sec-fetch-mode: cors
354
+ # on every request but never sets Sec-Fetch-Dest. Combined with a non-browser
355
+ # UA, this must NOT be classified as a browser or the agent never sees the
356
+ # 402 JSON it needs to read to pay.
357
+ assert not is_browser_from_headers({
358
+ "sec-fetch-mode": "cors",
359
+ "user-agent": "my-agent/1.0",
360
+ })
361
+
343
362
 
344
363
  class TestPublicPath:
345
364
  def test_robots_txt(self):