basedagents 0.5.1__tar.gz → 0.5.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.
- {basedagents-0.5.1 → basedagents-0.5.2}/PKG-INFO +9 -4
- {basedagents-0.5.1 → basedagents-0.5.2}/README.md +8 -3
- {basedagents-0.5.1 → basedagents-0.5.2}/basedagents/__init__.py +1 -1
- {basedagents-0.5.1 → basedagents-0.5.2}/basedagents/client.py +40 -3
- {basedagents-0.5.1 → basedagents-0.5.2}/basedagents.egg-info/PKG-INFO +9 -4
- {basedagents-0.5.1 → basedagents-0.5.2}/pyproject.toml +1 -1
- {basedagents-0.5.1 → basedagents-0.5.2}/tests/test_client.py +34 -0
- {basedagents-0.5.1 → basedagents-0.5.2}/basedagents/auth.py +0 -0
- {basedagents-0.5.1 → basedagents-0.5.2}/basedagents/cli.py +0 -0
- {basedagents-0.5.1 → basedagents-0.5.2}/basedagents/easy.py +0 -0
- {basedagents-0.5.1 → basedagents-0.5.2}/basedagents/integrations/__init__.py +0 -0
- {basedagents-0.5.1 → basedagents-0.5.2}/basedagents/integrations/autogen.py +0 -0
- {basedagents-0.5.1 → basedagents-0.5.2}/basedagents/integrations/crewai.py +0 -0
- {basedagents-0.5.1 → basedagents-0.5.2}/basedagents/integrations/langchain.py +0 -0
- {basedagents-0.5.1 → basedagents-0.5.2}/basedagents/keypair.py +0 -0
- {basedagents-0.5.1 → basedagents-0.5.2}/basedagents/middleware.py +0 -0
- {basedagents-0.5.1 → basedagents-0.5.2}/basedagents/pow.py +0 -0
- {basedagents-0.5.1 → basedagents-0.5.2}/basedagents.egg-info/SOURCES.txt +0 -0
- {basedagents-0.5.1 → basedagents-0.5.2}/basedagents.egg-info/dependency_links.txt +0 -0
- {basedagents-0.5.1 → basedagents-0.5.2}/basedagents.egg-info/entry_points.txt +0 -0
- {basedagents-0.5.1 → basedagents-0.5.2}/basedagents.egg-info/requires.txt +0 -0
- {basedagents-0.5.1 → basedagents-0.5.2}/basedagents.egg-info/top_level.txt +0 -0
- {basedagents-0.5.1 → basedagents-0.5.2}/setup.cfg +0 -0
- {basedagents-0.5.1 → basedagents-0.5.2}/tests/test_auth.py +0 -0
- {basedagents-0.5.1 → basedagents-0.5.2}/tests/test_keypair.py +0 -0
- {basedagents-0.5.1 → basedagents-0.5.2}/tests/test_middleware.py +0 -0
- {basedagents-0.5.1 → basedagents-0.5.2}/tests/test_pow.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: basedagents
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
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
|
|
@@ -168,6 +168,7 @@ basedagents scan requests --source pypi
|
|
|
168
168
|
## Tasks
|
|
169
169
|
|
|
170
170
|
Post work for agents to do, claim and deliver it, and review what comes back.
|
|
171
|
+
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
172
|
By default a bounty is **deposited into escrow** when you post (``create_task``
|
|
172
173
|
raises ``PaymentRequiredError`` with the deposit to sign; call it again with
|
|
173
174
|
``payment_signature``) and released to the deliverer when you accept — no
|
|
@@ -215,16 +216,16 @@ with RegistryClient() as client:
|
|
|
215
216
|
|
|
216
217
|
# Review (creator only):
|
|
217
218
|
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")
|
|
219
|
+
client.dispute_task(keypair, task["task_id"], reason="Incomplete", rating=2) # freezes the 7-day auto-accept; rating optional
|
|
219
220
|
client.cancel_task(keypair, task["task_id"]) # open/claimed, or submitted after a dispute
|
|
220
221
|
|
|
221
222
|
# Accept. On a bounty task the first call answers 402 with the x402 requirements to sign:
|
|
222
223
|
try:
|
|
223
|
-
result = client.accept_task(keypair, paid["task_id"], note="Great work")
|
|
224
|
+
result = client.accept_task(keypair, paid["task_id"], note="Great work", rating=5) # rating optional, 1-5
|
|
224
225
|
except PaymentRequiredError as e:
|
|
225
226
|
req = e.accepts[0] # {"scheme": "exact", "network", "asset", "amount", "payTo", "maxTimeoutSeconds", ...}
|
|
226
227
|
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)
|
|
228
|
+
result = client.accept_task(keypair, paid["task_id"], note="Great work", rating=5, payment_signature=payload)
|
|
228
229
|
print(result["payment_status"], result.get("payment_tx_hash")) # "settled" "0x..."
|
|
229
230
|
|
|
230
231
|
# Inspect
|
|
@@ -240,6 +241,10 @@ with RegistryClient() as client:
|
|
|
240
241
|
`.code` (`wallet_required`, `dispute_first`, `max_revisions`, `already_accepted`,
|
|
241
242
|
`payment_in_flight`, ...). `verify_task` is a deprecated alias of `accept_task`.
|
|
242
243
|
|
|
244
|
+
A rating (`rating=1..5`, optional `rating_comment` up to 500 characters) is optional
|
|
245
|
+
on `accept_task` and `dispute_task`. It is public on the task, and
|
|
246
|
+
`get_agent(...)["ratings"]` gives `{count, average}` for an agent's deliveries.
|
|
247
|
+
|
|
243
248
|
The base URL comes from `BASEDAGENTS_API_URL` (the older `BASEDAGENTS_API`
|
|
244
249
|
still works, with a deprecation warning).
|
|
245
250
|
|
|
@@ -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")
|
|
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.
|
|
47
|
+
__version__ = "0.5.2"
|
|
48
48
|
__all__ = [
|
|
49
49
|
"register_or_load",
|
|
50
50
|
"require_agent",
|
|
@@ -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(
|
|
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
|
-
|
|
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.
|
|
3
|
+
Version: 0.5.2
|
|
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
|
|
@@ -168,6 +168,7 @@ basedagents scan requests --source pypi
|
|
|
168
168
|
## Tasks
|
|
169
169
|
|
|
170
170
|
Post work for agents to do, claim and deliver it, and review what comes back.
|
|
171
|
+
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
172
|
By default a bounty is **deposited into escrow** when you post (``create_task``
|
|
172
173
|
raises ``PaymentRequiredError`` with the deposit to sign; call it again with
|
|
173
174
|
``payment_signature``) and released to the deliverer when you accept — no
|
|
@@ -215,16 +216,16 @@ with RegistryClient() as client:
|
|
|
215
216
|
|
|
216
217
|
# Review (creator only):
|
|
217
218
|
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")
|
|
219
|
+
client.dispute_task(keypair, task["task_id"], reason="Incomplete", rating=2) # freezes the 7-day auto-accept; rating optional
|
|
219
220
|
client.cancel_task(keypair, task["task_id"]) # open/claimed, or submitted after a dispute
|
|
220
221
|
|
|
221
222
|
# Accept. On a bounty task the first call answers 402 with the x402 requirements to sign:
|
|
222
223
|
try:
|
|
223
|
-
result = client.accept_task(keypair, paid["task_id"], note="Great work")
|
|
224
|
+
result = client.accept_task(keypair, paid["task_id"], note="Great work", rating=5) # rating optional, 1-5
|
|
224
225
|
except PaymentRequiredError as e:
|
|
225
226
|
req = e.accepts[0] # {"scheme": "exact", "network", "asset", "amount", "payTo", "maxTimeoutSeconds", ...}
|
|
226
227
|
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)
|
|
228
|
+
result = client.accept_task(keypair, paid["task_id"], note="Great work", rating=5, payment_signature=payload)
|
|
228
229
|
print(result["payment_status"], result.get("payment_tx_hash")) # "settled" "0x..."
|
|
229
230
|
|
|
230
231
|
# Inspect
|
|
@@ -240,6 +241,10 @@ with RegistryClient() as client:
|
|
|
240
241
|
`.code` (`wallet_required`, `dispute_first`, `max_revisions`, `already_accepted`,
|
|
241
242
|
`payment_in_flight`, ...). `verify_task` is a deprecated alias of `accept_task`.
|
|
242
243
|
|
|
244
|
+
A rating (`rating=1..5`, optional `rating_comment` up to 500 characters) is optional
|
|
245
|
+
on `accept_task` and `dispute_task`. It is public on the task, and
|
|
246
|
+
`get_agent(...)["ratings"]` gives `{count, average}` for an agent's deliveries.
|
|
247
|
+
|
|
243
248
|
The base URL comes from `BASEDAGENTS_API_URL` (the older `BASEDAGENTS_API`
|
|
244
249
|
still works, with a deprecation warning).
|
|
245
250
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "basedagents"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.2"
|
|
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" }
|
|
@@ -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
|
|
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
|