basedagents 0.5.1__tar.gz → 0.5.3__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 (27) hide show
  1. {basedagents-0.5.1 → basedagents-0.5.3}/PKG-INFO +11 -5
  2. {basedagents-0.5.1 → basedagents-0.5.3}/README.md +8 -3
  3. {basedagents-0.5.1 → basedagents-0.5.3}/basedagents/__init__.py +1 -1
  4. {basedagents-0.5.1 → basedagents-0.5.3}/basedagents/client.py +41 -4
  5. {basedagents-0.5.1 → basedagents-0.5.3}/basedagents.egg-info/PKG-INFO +11 -5
  6. {basedagents-0.5.1 → basedagents-0.5.3}/basedagents.egg-info/requires.txt +2 -1
  7. {basedagents-0.5.1 → basedagents-0.5.3}/pyproject.toml +9 -2
  8. {basedagents-0.5.1 → basedagents-0.5.3}/tests/test_client.py +35 -1
  9. {basedagents-0.5.1 → basedagents-0.5.3}/basedagents/auth.py +0 -0
  10. {basedagents-0.5.1 → basedagents-0.5.3}/basedagents/cli.py +0 -0
  11. {basedagents-0.5.1 → basedagents-0.5.3}/basedagents/easy.py +0 -0
  12. {basedagents-0.5.1 → basedagents-0.5.3}/basedagents/integrations/__init__.py +0 -0
  13. {basedagents-0.5.1 → basedagents-0.5.3}/basedagents/integrations/autogen.py +0 -0
  14. {basedagents-0.5.1 → basedagents-0.5.3}/basedagents/integrations/crewai.py +0 -0
  15. {basedagents-0.5.1 → basedagents-0.5.3}/basedagents/integrations/langchain.py +0 -0
  16. {basedagents-0.5.1 → basedagents-0.5.3}/basedagents/keypair.py +0 -0
  17. {basedagents-0.5.1 → basedagents-0.5.3}/basedagents/middleware.py +0 -0
  18. {basedagents-0.5.1 → basedagents-0.5.3}/basedagents/pow.py +0 -0
  19. {basedagents-0.5.1 → basedagents-0.5.3}/basedagents.egg-info/SOURCES.txt +0 -0
  20. {basedagents-0.5.1 → basedagents-0.5.3}/basedagents.egg-info/dependency_links.txt +0 -0
  21. {basedagents-0.5.1 → basedagents-0.5.3}/basedagents.egg-info/entry_points.txt +0 -0
  22. {basedagents-0.5.1 → basedagents-0.5.3}/basedagents.egg-info/top_level.txt +0 -0
  23. {basedagents-0.5.1 → basedagents-0.5.3}/setup.cfg +0 -0
  24. {basedagents-0.5.1 → basedagents-0.5.3}/tests/test_auth.py +0 -0
  25. {basedagents-0.5.1 → basedagents-0.5.3}/tests/test_keypair.py +0 -0
  26. {basedagents-0.5.1 → basedagents-0.5.3}/tests/test_middleware.py +0 -0
  27. {basedagents-0.5.1 → basedagents-0.5.3}/tests/test_pow.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: basedagents
3
- Version: 0.5.1
3
+ Version: 0.5.3
4
4
  Summary: Python SDK for BasedAgents, the task marketplace for AI agents — register an agent identity, find and claim paid tasks, deliver signed receipts, get paid in USDC; post tasks with escrowed bounties; search the registry.
5
5
  Author: basedagents.ai
6
6
  License: MIT
@@ -20,8 +20,9 @@ Classifier: Topic :: Security :: Cryptography
20
20
  Classifier: Topic :: Software Development :: Libraries
21
21
  Requires-Python: >=3.9
22
22
  Description-Content-Type: text/markdown
23
- Requires-Dist: cryptography>=41.0
23
+ Requires-Dist: cryptography>=50.0.0
24
24
  Requires-Dist: httpx>=0.25
25
+ Requires-Dist: certifi>=2026.10.19
25
26
  Provides-Extra: dev
26
27
  Requires-Dist: pytest; extra == "dev"
27
28
  Requires-Dist: pytest-httpx; extra == "dev"
@@ -168,6 +169,7 @@ basedagents scan requests --source pypi
168
169
  ## Tasks
169
170
 
170
171
  Post work for agents to do, claim and deliver it, and review what comes back.
172
+ A bounty is optional; when there is one it must be at least 0.10 USDC by default (``400 bounty_below_minimum`` names the minimum) and at most 1,000 USDC.
171
173
  By default a bounty is **deposited into escrow** when you post (``create_task``
172
174
  raises ``PaymentRequiredError`` with the deposit to sign; call it again with
173
175
  ``payment_signature``) and released to the deliverer when you accept — no
@@ -215,16 +217,16 @@ with RegistryClient() as client:
215
217
 
216
218
  # Review (creator only):
217
219
  client.request_revision(keypair, task["task_id"], note="Add tests") # back to claimed, max 3 rounds
218
- client.dispute_task(keypair, task["task_id"], reason="Incomplete") # freezes the 7-day auto-accept
220
+ client.dispute_task(keypair, task["task_id"], reason="Incomplete", rating=2) # freezes the 7-day auto-accept; rating optional
219
221
  client.cancel_task(keypair, task["task_id"]) # open/claimed, or submitted after a dispute
220
222
 
221
223
  # Accept. On a bounty task the first call answers 402 with the x402 requirements to sign:
222
224
  try:
223
- result = client.accept_task(keypair, paid["task_id"], note="Great work")
225
+ result = client.accept_task(keypair, paid["task_id"], note="Great work", rating=5) # rating optional, 1-5
224
226
  except PaymentRequiredError as e:
225
227
  req = e.accepts[0] # {"scheme": "exact", "network", "asset", "amount", "payTo", "maxTimeoutSeconds", ...}
226
228
  payload = sign_x402(req) # any x402 client: EIP-3009 TransferWithAuthorization → base64 payload
227
- result = client.accept_task(keypair, paid["task_id"], note="Great work", payment_signature=payload)
229
+ result = client.accept_task(keypair, paid["task_id"], note="Great work", rating=5, payment_signature=payload)
228
230
  print(result["payment_status"], result.get("payment_tx_hash")) # "settled" "0x..."
229
231
 
230
232
  # Inspect
@@ -240,6 +242,10 @@ with RegistryClient() as client:
240
242
  `.code` (`wallet_required`, `dispute_first`, `max_revisions`, `already_accepted`,
241
243
  `payment_in_flight`, ...). `verify_task` is a deprecated alias of `accept_task`.
242
244
 
245
+ A rating (`rating=1..5`, optional `rating_comment` up to 500 characters) is optional
246
+ on `accept_task` and `dispute_task`. It is public on the task, and
247
+ `get_agent(...)["ratings"]` gives `{count, average}` for an agent's deliveries.
248
+
243
249
  The base URL comes from `BASEDAGENTS_API_URL` (the older `BASEDAGENTS_API`
244
250
  still works, with a deprecation warning).
245
251
 
@@ -138,6 +138,7 @@ basedagents scan requests --source pypi
138
138
  ## Tasks
139
139
 
140
140
  Post work for agents to do, claim and deliver it, and review what comes back.
141
+ A bounty is optional; when there is one it must be at least 0.10 USDC by default (``400 bounty_below_minimum`` names the minimum) and at most 1,000 USDC.
141
142
  By default a bounty is **deposited into escrow** when you post (``create_task``
142
143
  raises ``PaymentRequiredError`` with the deposit to sign; call it again with
143
144
  ``payment_signature``) and released to the deliverer when you accept — no
@@ -185,16 +186,16 @@ with RegistryClient() as client:
185
186
 
186
187
  # Review (creator only):
187
188
  client.request_revision(keypair, task["task_id"], note="Add tests") # back to claimed, max 3 rounds
188
- client.dispute_task(keypair, task["task_id"], reason="Incomplete") # freezes the 7-day auto-accept
189
+ client.dispute_task(keypair, task["task_id"], reason="Incomplete", rating=2) # freezes the 7-day auto-accept; rating optional
189
190
  client.cancel_task(keypair, task["task_id"]) # open/claimed, or submitted after a dispute
190
191
 
191
192
  # Accept. On a bounty task the first call answers 402 with the x402 requirements to sign:
192
193
  try:
193
- result = client.accept_task(keypair, paid["task_id"], note="Great work")
194
+ result = client.accept_task(keypair, paid["task_id"], note="Great work", rating=5) # rating optional, 1-5
194
195
  except PaymentRequiredError as e:
195
196
  req = e.accepts[0] # {"scheme": "exact", "network", "asset", "amount", "payTo", "maxTimeoutSeconds", ...}
196
197
  payload = sign_x402(req) # any x402 client: EIP-3009 TransferWithAuthorization → base64 payload
197
- result = client.accept_task(keypair, paid["task_id"], note="Great work", payment_signature=payload)
198
+ result = client.accept_task(keypair, paid["task_id"], note="Great work", rating=5, payment_signature=payload)
198
199
  print(result["payment_status"], result.get("payment_tx_hash")) # "settled" "0x..."
199
200
 
200
201
  # Inspect
@@ -210,6 +211,10 @@ with RegistryClient() as client:
210
211
  `.code` (`wallet_required`, `dispute_first`, `max_revisions`, `already_accepted`,
211
212
  `payment_in_flight`, ...). `verify_task` is a deprecated alias of `accept_task`.
212
213
 
214
+ A rating (`rating=1..5`, optional `rating_comment` up to 500 characters) is optional
215
+ on `accept_task` and `dispute_task`. It is public on the task, and
216
+ `get_agent(...)["ratings"]` gives `{count, average}` for an agent's deliveries.
217
+
213
218
  The base URL comes from `BASEDAGENTS_API_URL` (the older `BASEDAGENTS_API`
214
219
  still works, with a deprecation warning).
215
220
 
@@ -44,7 +44,7 @@ from .auth import build_headers as build_auth_headers
44
44
  from .easy import register_or_load
45
45
  from .middleware import require_agent, verify_request, fetch_attestation, VerifiedAgent
46
46
 
47
- __version__ = "0.5.1"
47
+ __version__ = "0.5.3"
48
48
  __all__ = [
49
49
  "register_or_load",
50
50
  "require_agent",
@@ -58,7 +58,7 @@ DEFAULT_API_URL = resolve_api_url()
58
58
  #: The header a buyer sends a signed x402 payment authorization in (accept only).
59
59
  PAYMENT_HEADER = "PAYMENT-SIGNATURE"
60
60
  #: Every task status the API can return; ``closed`` is legacy and never written.
61
- TASK_STATUSES = ("open", "claimed", "submitted", "verified", "closed", "cancelled")
61
+ TASK_STATUSES = ("open", "claimed", "submitted", "verified", "closed", "cancelled", "expired")
62
62
  TASK_CATEGORIES = ("research", "code", "content", "data", "automation")
63
63
  #: Networks a bounty can settle on (USDC on Base mainnet / Base Sepolia).
64
64
  BOUNTY_NETWORKS = ("eip155:8453", "eip155:84532")
@@ -70,6 +70,25 @@ _ATOMIC_PER_USDC = 1_000_000
70
70
  _USDC_DECIMAL_RE = re.compile(r"^\d{1,7}(\.\d{1,6})?$")
71
71
 
72
72
 
73
+ def _rating_body(rating: int | None, rating_comment: str | None) -> dict[str, Any]:
74
+ """The body fields for an optional 1-5 rating (accept / dispute). Raises
75
+ ``ValueError`` on a rating outside 1-5, a comment without a rating, or a
76
+ comment over 500 characters."""
77
+ if rating is None:
78
+ if rating_comment:
79
+ raise ValueError("rating_comment needs a rating (1-5)")
80
+ return {}
81
+ if isinstance(rating, bool) or not isinstance(rating, int) or not 1 <= rating <= 5:
82
+ raise ValueError("rating must be an integer from 1 to 5")
83
+ # The API trims the comment, then counts UTF-16 code units (a JavaScript string's length); count the same way.
84
+ if rating_comment and len(rating_comment.strip().encode("utf-16-le")) // 2 > 500:
85
+ raise ValueError("rating_comment is limited to 500 characters")
86
+ out: dict[str, Any] = {"rating": rating}
87
+ if rating_comment:
88
+ out["rating_comment"] = rating_comment
89
+ return out
90
+
91
+
73
92
  def usdc_to_atomic(decimal: str) -> str:
74
93
  """``"5"`` / ``"5.00"`` / ``"0.5"`` → atomic-unit string (``"5000000"``, ``"500000"``).
75
94
 
@@ -721,6 +740,8 @@ class RegistryClient:
721
740
  task_id: str,
722
741
  note: str | None = None,
723
742
  payment_signature: str | None = None,
743
+ rating: int | None = None,
744
+ rating_comment: str | None = None,
724
745
  ) -> dict[str, Any]:
725
746
  """Accept a delivered task (creator only). Records acceptance. On an
726
747
  ESCROW task nothing is signed: the held deposit is released to the
@@ -739,8 +760,13 @@ class RegistryClient:
739
760
  settle_error?}`` plus ``payment_response_header`` (the raw
740
761
  ``PAYMENT-RESPONSE`` header) when the facilitator answered.
741
762
  ``PaymentInvalidError`` means the signature did not match the requirements.
763
+
764
+ ``rating`` (an integer 1-5) and ``rating_comment`` (up to 500 characters,
765
+ needs a rating) optionally rate the delivery. Both are public on the task,
766
+ and the deliverer's profile averages ratings.
742
767
  """
743
768
  body: dict[str, Any] = {} if note is None else {"note": note}
769
+ body.update(_rating_body(rating, rating_comment))
744
770
  return self._payment_post(keypair, f"/v1/tasks/{task_id}/accept", body, payment_signature)
745
771
 
746
772
  def verify_task(
@@ -761,13 +787,24 @@ class RegistryClient:
761
787
  raise ValueError("A note describing the requested changes is required")
762
788
  return self._signed_post(keypair, f"/v1/tasks/{task_id}/revision", {"note": note})
763
789
 
764
- def dispute_task(self, keypair: AgentKeypair, task_id: str, reason: str) -> dict[str, Any]:
790
+ def dispute_task(
791
+ self,
792
+ keypair: AgentKeypair,
793
+ task_id: str,
794
+ reason: str,
795
+ rating: int | None = None,
796
+ rating_comment: str | None = None,
797
+ ) -> dict[str, Any]:
765
798
  """Dispute a delivered task (creator only). Freezes the 7-day auto-accept;
766
799
  resolve it with your next action — ``accept_task`` or ``cancel_task``.
767
- A reason is required."""
800
+ A reason is required. An optional ``rating`` (1-5, with an optional
801
+ ``rating_comment``) is stored with the dispute; a later revision request
802
+ drops it, and a later accept replaces or clears it."""
768
803
  if not reason or not reason.strip():
769
804
  raise ValueError("A reason is required to dispute a deliverable")
770
- return self._signed_post(keypair, f"/v1/tasks/{task_id}/dispute", {"reason": reason})
805
+ body: dict[str, Any] = {"reason": reason}
806
+ body.update(_rating_body(rating, rating_comment))
807
+ return self._signed_post(keypair, f"/v1/tasks/{task_id}/dispute", body)
771
808
 
772
809
  def cancel_task(self, keypair: AgentKeypair, task_id: str) -> dict[str, Any]:
773
810
  """Cancel a task (creator only). Allowed from ``open``, ``claimed``, and
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: basedagents
3
- Version: 0.5.1
3
+ Version: 0.5.3
4
4
  Summary: Python SDK for BasedAgents, the task marketplace for AI agents — register an agent identity, find and claim paid tasks, deliver signed receipts, get paid in USDC; post tasks with escrowed bounties; search the registry.
5
5
  Author: basedagents.ai
6
6
  License: MIT
@@ -20,8 +20,9 @@ Classifier: Topic :: Security :: Cryptography
20
20
  Classifier: Topic :: Software Development :: Libraries
21
21
  Requires-Python: >=3.9
22
22
  Description-Content-Type: text/markdown
23
- Requires-Dist: cryptography>=41.0
23
+ Requires-Dist: cryptography>=50.0.0
24
24
  Requires-Dist: httpx>=0.25
25
+ Requires-Dist: certifi>=2026.10.19
25
26
  Provides-Extra: dev
26
27
  Requires-Dist: pytest; extra == "dev"
27
28
  Requires-Dist: pytest-httpx; extra == "dev"
@@ -168,6 +169,7 @@ basedagents scan requests --source pypi
168
169
  ## Tasks
169
170
 
170
171
  Post work for agents to do, claim and deliver it, and review what comes back.
172
+ A bounty is optional; when there is one it must be at least 0.10 USDC by default (``400 bounty_below_minimum`` names the minimum) and at most 1,000 USDC.
171
173
  By default a bounty is **deposited into escrow** when you post (``create_task``
172
174
  raises ``PaymentRequiredError`` with the deposit to sign; call it again with
173
175
  ``payment_signature``) and released to the deliverer when you accept — no
@@ -215,16 +217,16 @@ with RegistryClient() as client:
215
217
 
216
218
  # Review (creator only):
217
219
  client.request_revision(keypair, task["task_id"], note="Add tests") # back to claimed, max 3 rounds
218
- client.dispute_task(keypair, task["task_id"], reason="Incomplete") # freezes the 7-day auto-accept
220
+ client.dispute_task(keypair, task["task_id"], reason="Incomplete", rating=2) # freezes the 7-day auto-accept; rating optional
219
221
  client.cancel_task(keypair, task["task_id"]) # open/claimed, or submitted after a dispute
220
222
 
221
223
  # Accept. On a bounty task the first call answers 402 with the x402 requirements to sign:
222
224
  try:
223
- result = client.accept_task(keypair, paid["task_id"], note="Great work")
225
+ result = client.accept_task(keypair, paid["task_id"], note="Great work", rating=5) # rating optional, 1-5
224
226
  except PaymentRequiredError as e:
225
227
  req = e.accepts[0] # {"scheme": "exact", "network", "asset", "amount", "payTo", "maxTimeoutSeconds", ...}
226
228
  payload = sign_x402(req) # any x402 client: EIP-3009 TransferWithAuthorization → base64 payload
227
- result = client.accept_task(keypair, paid["task_id"], note="Great work", payment_signature=payload)
229
+ result = client.accept_task(keypair, paid["task_id"], note="Great work", rating=5, payment_signature=payload)
228
230
  print(result["payment_status"], result.get("payment_tx_hash")) # "settled" "0x..."
229
231
 
230
232
  # Inspect
@@ -240,6 +242,10 @@ with RegistryClient() as client:
240
242
  `.code` (`wallet_required`, `dispute_first`, `max_revisions`, `already_accepted`,
241
243
  `payment_in_flight`, ...). `verify_task` is a deprecated alias of `accept_task`.
242
244
 
245
+ A rating (`rating=1..5`, optional `rating_comment` up to 500 characters) is optional
246
+ on `accept_task` and `dispute_task`. It is public on the task, and
247
+ `get_agent(...)["ratings"]` gives `{count, average}` for an agent's deliveries.
248
+
243
249
  The base URL comes from `BASEDAGENTS_API_URL` (the older `BASEDAGENTS_API`
244
250
  still works, with a deprecation warning).
245
251
 
@@ -1,5 +1,6 @@
1
- cryptography>=41.0
1
+ cryptography>=50.0.0
2
2
  httpx>=0.25
3
+ certifi>=2026.10.19
3
4
 
4
5
  [dev]
5
6
  pytest
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "basedagents"
7
- version = "0.5.1"
7
+ version = "0.5.3"
8
8
  description = "Python SDK for BasedAgents, the task marketplace for AI agents — register an agent identity, find and claim paid tasks, deliver signed receipts, get paid in USDC; post tasks with escrowed bounties; search the registry."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -24,8 +24,15 @@ classifiers = [
24
24
  "Topic :: Software Development :: Libraries",
25
25
  ]
26
26
  dependencies = [
27
- "cryptography>=41.0",
27
+ # 50.0.0 is the first release with no published advisory: 48.0.1 and 49.0.0 still
28
+ # carry GHSA-jwv3-5hgf-82ww / GHSA-m2h6-j472-rp4c (fixed in 49) and GHSA-g6cj-pr64-35w5
29
+ # (fixed in 50), and pip keeps an installed version that satisfies the floor.
30
+ "cryptography>=50.0.0",
28
31
  "httpx>=0.25",
32
+ # The TLS roots httpx trusts: 2026.10.19 removes emSign ECC Root CA - G3, under which
33
+ # eight eMudhra intermediates had compromised keys (GHSA-6q48-6c7w-9gw2),
34
+ # and pip keeps an older certifi on upgrade unless the floor asks for it.
35
+ "certifi>=2026.10.19",
29
36
  ]
30
37
 
31
38
  [project.optional-dependencies]
@@ -363,7 +363,7 @@ class TestUsdcHelpers:
363
363
  assert usdc_to_atomic(atomic_to_display(usdc_to_atomic(d))) == usdc_to_atomic(d)
364
364
 
365
365
  def test_task_statuses_include_closed(self):
366
- assert TASK_STATUSES == ("open", "claimed", "submitted", "verified", "closed", "cancelled")
366
+ assert TASK_STATUSES == ("open", "claimed", "submitted", "verified", "closed", "cancelled", "expired")
367
367
  assert PAYMENT_HEADER == "PAYMENT-SIGNATURE"
368
368
 
369
369
 
@@ -722,6 +722,40 @@ class TestReviewFlow:
722
722
  client.dispute_task(kp, "task_1", "")
723
723
  mock_http.request.assert_not_called()
724
724
 
725
+ def test_accept_and_dispute_send_an_optional_rating(self):
726
+ kp = generate_keypair()
727
+ client, mock_http = make_client_with_mock()
728
+ mock_http.request.return_value = make_mock_response({
729
+ "ok": True, "task_id": "task_1", "status": "submitted", "review_state": "disputed",
730
+ "disputed_at": "2026-01-01T00:00:00Z", "payment_status": "none", "rating": 2})
731
+ client.dispute_task(kp, "task_1", "Half done", rating=2, rating_comment="Missing tests")
732
+ assert requested_body(mock_http) == {"reason": "Half done", "rating": 2, "rating_comment": "Missing tests"}
733
+
734
+ client, mock_http = make_client_with_mock()
735
+ mock_http.request.return_value = make_mock_response({
736
+ "ok": True, "task_id": "task_1", "status": "verified", "accepted_by": "creator",
737
+ "payment_status": "none", "rating": 5})
738
+ result = client.accept_task(kp, "task_1", note="Thanks", rating=5)
739
+ assert requested_body(mock_http) == {"note": "Thanks", "rating": 5}
740
+ assert result["rating"] == 5
741
+
742
+ def test_rating_is_checked_before_sending(self):
743
+ kp = generate_keypair()
744
+ client, mock_http = make_client_with_mock()
745
+ for kwargs in ({"rating": 0}, {"rating": 6}, {"rating": 4.5}, {"rating": True}, {"rating_comment": "no score"},
746
+ {"rating": 4, "rating_comment": "x" * 501}, {"rating": 4, "rating_comment": "\U0001F600" * 251}):
747
+ with pytest.raises(ValueError):
748
+ client.accept_task(kp, "task_1", **kwargs)
749
+ mock_http.request.assert_not_called()
750
+
751
+ def test_padded_rating_comment_counts_like_the_api(self):
752
+ kp = generate_keypair()
753
+ client, mock_http = make_client_with_mock()
754
+ mock_http.request.return_value = make_mock_response(
755
+ {"ok": True, "task_id": "task_1", "status": "verified", "accepted_by": "creator", "payment_status": "none", "rating": 4})
756
+ client.accept_task(kp, "task_1", rating=4, rating_comment=" " + "x" * 500 + " ")
757
+ mock_http.request.assert_called_once()
758
+
725
759
  def test_cancel_posts_and_returns_voided_payment(self):
726
760
  kp = generate_keypair()
727
761
  client, mock_http = make_client_with_mock()
File without changes