agent0-sdk 0.2.2__py3-none-any.whl → 0.5__py3-none-any.whl
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.
- agent0_sdk/__init__.py +1 -1
- agent0_sdk/core/agent.py +303 -29
- agent0_sdk/core/contracts.py +93 -51
- agent0_sdk/core/feedback_manager.py +191 -162
- agent0_sdk/core/indexer.py +787 -37
- agent0_sdk/core/models.py +10 -21
- agent0_sdk/core/oasf_validator.py +98 -0
- agent0_sdk/core/sdk.py +238 -20
- agent0_sdk/core/subgraph_client.py +56 -17
- agent0_sdk/core/web3_client.py +184 -17
- agent0_sdk/taxonomies/all_domains.json +1565 -0
- agent0_sdk/taxonomies/all_skills.json +1030 -0
- {agent0_sdk-0.2.2.dist-info → agent0_sdk-0.5.dist-info}/METADATA +78 -6
- agent0_sdk-0.5.dist-info/RECORD +19 -0
- {agent0_sdk-0.2.2.dist-info → agent0_sdk-0.5.dist-info}/top_level.txt +0 -1
- agent0_sdk-0.2.2.dist-info/RECORD +0 -27
- tests/__init__.py +0 -1
- tests/config.py +0 -46
- tests/conftest.py +0 -22
- tests/test_feedback.py +0 -417
- tests/test_models.py +0 -224
- tests/test_real_public_servers.py +0 -103
- tests/test_registration.py +0 -267
- tests/test_registrationIpfs.py +0 -227
- tests/test_sdk.py +0 -240
- tests/test_search.py +0 -415
- tests/test_transfer.py +0 -255
- {agent0_sdk-0.2.2.dist-info → agent0_sdk-0.5.dist-info}/WHEEL +0 -0
- {agent0_sdk-0.2.2.dist-info → agent0_sdk-0.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -30,25 +30,38 @@ class SubgraphClient:
|
|
|
30
30
|
Returns:
|
|
31
31
|
JSON response from the subgraph
|
|
32
32
|
"""
|
|
33
|
-
|
|
33
|
+
def _do_query(q: str) -> Dict[str, Any]:
|
|
34
34
|
response = requests.post(
|
|
35
35
|
self.subgraph_url,
|
|
36
|
-
json={
|
|
37
|
-
'query': query,
|
|
38
|
-
'variables': variables or {}
|
|
39
|
-
},
|
|
36
|
+
json={'query': q, 'variables': variables or {}},
|
|
40
37
|
headers={'Content-Type': 'application/json'},
|
|
41
|
-
timeout=
|
|
38
|
+
timeout=10,
|
|
42
39
|
)
|
|
43
40
|
response.raise_for_status()
|
|
44
41
|
result = response.json()
|
|
45
|
-
|
|
46
|
-
# Check for GraphQL errors
|
|
47
42
|
if 'errors' in result:
|
|
48
43
|
error_messages = [err.get('message', 'Unknown error') for err in result['errors']]
|
|
49
44
|
raise ValueError(f"GraphQL errors: {', '.join(error_messages)}")
|
|
50
|
-
|
|
51
45
|
return result.get('data', {})
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
return _do_query(query)
|
|
49
|
+
except ValueError as e:
|
|
50
|
+
# Backwards/forwards compatibility for hosted subgraphs:
|
|
51
|
+
# Some deployments still expose `responseUri` instead of `responseURI`.
|
|
52
|
+
msg = str(e)
|
|
53
|
+
if ("has no field" in msg and "responseURI" in msg) and ("responseURI" in query):
|
|
54
|
+
logger.debug("Subgraph schema missing responseURI; retrying query with responseUri")
|
|
55
|
+
return _do_query(query.replace("responseURI", "responseUri"))
|
|
56
|
+
# Some deployments don't expose agentWallet fields on AgentRegistrationFile.
|
|
57
|
+
if (
|
|
58
|
+
"Type `AgentRegistrationFile` has no field `agentWallet`" in msg
|
|
59
|
+
or "Type `AgentRegistrationFile` has no field `agentWalletChainId`" in msg
|
|
60
|
+
):
|
|
61
|
+
logger.debug("Subgraph schema missing agentWallet fields; retrying query without them")
|
|
62
|
+
q2 = query.replace("agentWalletChainId", "").replace("agentWallet", "")
|
|
63
|
+
return _do_query(q2)
|
|
64
|
+
raise
|
|
52
65
|
except requests.exceptions.RequestException as e:
|
|
53
66
|
raise ConnectionError(f"Failed to query subgraph: {e}")
|
|
54
67
|
|
|
@@ -248,10 +261,12 @@ class SubgraphClient:
|
|
|
248
261
|
) {{
|
|
249
262
|
id
|
|
250
263
|
score
|
|
264
|
+
feedbackIndex
|
|
251
265
|
tag1
|
|
252
266
|
tag2
|
|
267
|
+
endpoint
|
|
253
268
|
clientAddress
|
|
254
|
-
|
|
269
|
+
feedbackURI
|
|
255
270
|
feedbackURIType
|
|
256
271
|
feedbackHash
|
|
257
272
|
isRevoked
|
|
@@ -276,7 +291,7 @@ class SubgraphClient:
|
|
|
276
291
|
responses {{
|
|
277
292
|
id
|
|
278
293
|
responder
|
|
279
|
-
|
|
294
|
+
responseURI
|
|
280
295
|
responseHash
|
|
281
296
|
createdAt
|
|
282
297
|
}}
|
|
@@ -400,10 +415,12 @@ class SubgraphClient:
|
|
|
400
415
|
id
|
|
401
416
|
agent { id agentId chainId }
|
|
402
417
|
clientAddress
|
|
418
|
+
feedbackIndex
|
|
403
419
|
score
|
|
404
420
|
tag1
|
|
405
421
|
tag2
|
|
406
|
-
|
|
422
|
+
endpoint
|
|
423
|
+
feedbackURI
|
|
407
424
|
feedbackURIType
|
|
408
425
|
feedbackHash
|
|
409
426
|
isRevoked
|
|
@@ -429,7 +446,7 @@ class SubgraphClient:
|
|
|
429
446
|
responses {
|
|
430
447
|
id
|
|
431
448
|
responder
|
|
432
|
-
|
|
449
|
+
responseURI
|
|
433
450
|
responseHash
|
|
434
451
|
createdAt
|
|
435
452
|
}
|
|
@@ -548,10 +565,12 @@ class SubgraphClient:
|
|
|
548
565
|
id
|
|
549
566
|
agent {{ id agentId chainId }}
|
|
550
567
|
clientAddress
|
|
568
|
+
feedbackIndex
|
|
551
569
|
score
|
|
552
570
|
tag1
|
|
553
571
|
tag2
|
|
554
|
-
|
|
572
|
+
endpoint
|
|
573
|
+
feedbackURI
|
|
555
574
|
feedbackURIType
|
|
556
575
|
feedbackHash
|
|
557
576
|
isRevoked
|
|
@@ -577,7 +596,7 @@ class SubgraphClient:
|
|
|
577
596
|
responses {{
|
|
578
597
|
id
|
|
579
598
|
responder
|
|
580
|
-
|
|
599
|
+
responseURI
|
|
581
600
|
responseHash
|
|
582
601
|
createdAt
|
|
583
602
|
}}
|
|
@@ -716,12 +735,14 @@ class SubgraphClient:
|
|
|
716
735
|
return []
|
|
717
736
|
else:
|
|
718
737
|
# No feedback filters - query agents directly
|
|
719
|
-
|
|
738
|
+
# For reputation search, we want agents that have feedback
|
|
739
|
+
# Filter by totalFeedback > 0 to only get agents with feedback
|
|
740
|
+
agent_filters = ['totalFeedback_gt: 0'] # Only agents with feedback (BigInt comparison)
|
|
720
741
|
if agents is not None and len(agents) > 0:
|
|
721
742
|
agent_ids = [f'"{aid}"' for aid in agents]
|
|
722
743
|
agent_filters.append(f'id_in: [{", ".join(agent_ids)}]')
|
|
723
744
|
|
|
724
|
-
agent_where = f"where: {{ {', '.join(agent_filters)} }}"
|
|
745
|
+
agent_where = f"where: {{ {', '.join(agent_filters)} }}"
|
|
725
746
|
|
|
726
747
|
# Build feedback where for agent query (to calculate scores)
|
|
727
748
|
feedback_where_for_agents = f"{{ {', '.join(feedback_filters)} }}" if feedback_filters else "{}"
|
|
@@ -784,6 +805,12 @@ class SubgraphClient:
|
|
|
784
805
|
|
|
785
806
|
try:
|
|
786
807
|
result = self.query(query)
|
|
808
|
+
|
|
809
|
+
# Check for GraphQL errors
|
|
810
|
+
if 'errors' in result:
|
|
811
|
+
logger.error(f"GraphQL errors in search_agents_by_reputation: {result['errors']}")
|
|
812
|
+
return []
|
|
813
|
+
|
|
787
814
|
agents_result = result.get('agents', [])
|
|
788
815
|
|
|
789
816
|
# Calculate average scores
|
|
@@ -806,6 +833,18 @@ class SubgraphClient:
|
|
|
806
833
|
if agent.get('averageScore') is not None and agent['averageScore'] >= minAverageScore
|
|
807
834
|
]
|
|
808
835
|
|
|
836
|
+
# For reputation search, filter logic:
|
|
837
|
+
# - If specific agents were requested, return them even if averageScore is None
|
|
838
|
+
# (the user explicitly asked for these agents, so return them)
|
|
839
|
+
# - If general search (no specific agents), only return agents with reputation data
|
|
840
|
+
if agents is None or len(agents) == 0:
|
|
841
|
+
# General search - only return agents with reputation
|
|
842
|
+
agents_result = [
|
|
843
|
+
agent for agent in agents_result
|
|
844
|
+
if agent.get('averageScore') is not None
|
|
845
|
+
]
|
|
846
|
+
# else: specific agents requested - return all requested agents (even if averageScore is None)
|
|
847
|
+
|
|
809
848
|
return agents_result
|
|
810
849
|
|
|
811
850
|
except Exception as e:
|
agent0_sdk/core/web3_client.py
CHANGED
|
@@ -5,7 +5,7 @@ Web3 integration layer for smart contract interactions.
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
7
|
import json
|
|
8
|
-
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
8
|
+
from typing import Any, Dict, List, Optional, Tuple, Union, Callable
|
|
9
9
|
|
|
10
10
|
try:
|
|
11
11
|
from web3 import Web3
|
|
@@ -123,22 +123,6 @@ class Web3Client:
|
|
|
123
123
|
|
|
124
124
|
return event_filter.get_all_entries()
|
|
125
125
|
|
|
126
|
-
def encodeFeedbackAuth(
|
|
127
|
-
self,
|
|
128
|
-
agentId: int,
|
|
129
|
-
clientAddress: str,
|
|
130
|
-
indexLimit: int,
|
|
131
|
-
expiry: int,
|
|
132
|
-
chainId: int,
|
|
133
|
-
identityRegistry: str,
|
|
134
|
-
signerAddress: str
|
|
135
|
-
) -> bytes:
|
|
136
|
-
"""Encode feedback authorization data."""
|
|
137
|
-
return self.w3.codec.encode(
|
|
138
|
-
['uint256', 'address', 'uint64', 'uint256', 'uint256', 'address', 'address'],
|
|
139
|
-
[agentId, clientAddress, indexLimit, expiry, chainId, identityRegistry, signerAddress]
|
|
140
|
-
)
|
|
141
|
-
|
|
142
126
|
def signMessage(self, message: bytes) -> bytes:
|
|
143
127
|
"""Sign a message with the account's private key."""
|
|
144
128
|
# Create a SignableMessage from the raw bytes
|
|
@@ -190,3 +174,186 @@ class Web3Client:
|
|
|
190
174
|
def get_transaction_count(self, address: str) -> int:
|
|
191
175
|
"""Get transaction count (nonce) of an address."""
|
|
192
176
|
return self.w3.eth.get_transaction_count(address)
|
|
177
|
+
|
|
178
|
+
def encodeEIP712Domain(
|
|
179
|
+
self,
|
|
180
|
+
name: str,
|
|
181
|
+
version: str,
|
|
182
|
+
chain_id: int,
|
|
183
|
+
verifying_contract: str
|
|
184
|
+
) -> Dict[str, Any]:
|
|
185
|
+
"""Encode EIP-712 domain separator.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
name: Contract name
|
|
189
|
+
version: Contract version
|
|
190
|
+
chain_id: Chain ID
|
|
191
|
+
verifying_contract: Contract address
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Domain separator dictionary
|
|
195
|
+
"""
|
|
196
|
+
return {
|
|
197
|
+
"name": name,
|
|
198
|
+
"version": version,
|
|
199
|
+
"chainId": chain_id,
|
|
200
|
+
"verifyingContract": verifying_contract
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
def build_agent_wallet_set_typed_data(
|
|
204
|
+
self,
|
|
205
|
+
agent_id: int,
|
|
206
|
+
new_wallet: str,
|
|
207
|
+
owner: str,
|
|
208
|
+
deadline: int,
|
|
209
|
+
verifying_contract: str,
|
|
210
|
+
chain_id: int,
|
|
211
|
+
) -> Dict[str, Any]:
|
|
212
|
+
"""Build EIP-712 typed data for ERC-8004 IdentityRegistry setAgentWallet.
|
|
213
|
+
|
|
214
|
+
Contract expects:
|
|
215
|
+
- domain: name="ERC8004IdentityRegistry", version="1"
|
|
216
|
+
- primaryType: "AgentWalletSet"
|
|
217
|
+
- message: { agentId, newWallet, owner, deadline }
|
|
218
|
+
"""
|
|
219
|
+
domain = self.encodeEIP712Domain(
|
|
220
|
+
name="ERC8004IdentityRegistry",
|
|
221
|
+
version="1",
|
|
222
|
+
chain_id=chain_id,
|
|
223
|
+
verifying_contract=verifying_contract,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
message_types = {
|
|
227
|
+
"AgentWalletSet": [
|
|
228
|
+
{"name": "agentId", "type": "uint256"},
|
|
229
|
+
{"name": "newWallet", "type": "address"},
|
|
230
|
+
{"name": "owner", "type": "address"},
|
|
231
|
+
{"name": "deadline", "type": "uint256"},
|
|
232
|
+
]
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
message = {
|
|
236
|
+
"agentId": agent_id,
|
|
237
|
+
"newWallet": new_wallet,
|
|
238
|
+
"owner": owner,
|
|
239
|
+
"deadline": deadline,
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
# eth_account.messages.encode_typed_data expects the "full_message" format
|
|
243
|
+
return {
|
|
244
|
+
"types": {
|
|
245
|
+
"EIP712Domain": [
|
|
246
|
+
{"name": "name", "type": "string"},
|
|
247
|
+
{"name": "version", "type": "string"},
|
|
248
|
+
{"name": "chainId", "type": "uint256"},
|
|
249
|
+
{"name": "verifyingContract", "type": "address"},
|
|
250
|
+
],
|
|
251
|
+
**message_types,
|
|
252
|
+
},
|
|
253
|
+
"domain": domain,
|
|
254
|
+
"primaryType": "AgentWalletSet",
|
|
255
|
+
"message": message,
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
def sign_typed_data(
|
|
259
|
+
self,
|
|
260
|
+
full_message: Dict[str, Any],
|
|
261
|
+
signer: Union[str, BaseAccount],
|
|
262
|
+
) -> bytes:
|
|
263
|
+
"""Sign EIP-712 typed data with a provided signer (EOA).
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
full_message: Typed data dict compatible with encode_typed_data(full_message=...)
|
|
267
|
+
signer: Private key string or eth_account BaseAccount/LocalAccount
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
Signature bytes
|
|
271
|
+
"""
|
|
272
|
+
from eth_account.messages import encode_typed_data
|
|
273
|
+
|
|
274
|
+
if isinstance(signer, str):
|
|
275
|
+
acct: BaseAccount = Account.from_key(signer)
|
|
276
|
+
else:
|
|
277
|
+
acct = signer
|
|
278
|
+
|
|
279
|
+
encoded = encode_typed_data(full_message=full_message)
|
|
280
|
+
signed = acct.sign_message(encoded)
|
|
281
|
+
return signed.signature
|
|
282
|
+
|
|
283
|
+
def signEIP712Message(
|
|
284
|
+
self,
|
|
285
|
+
domain: Dict[str, Any],
|
|
286
|
+
message_types: Dict[str, List[Dict[str, str]]],
|
|
287
|
+
message: Dict[str, Any]
|
|
288
|
+
) -> bytes:
|
|
289
|
+
"""Sign an EIP-712 typed message.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
domain: EIP-712 domain separator
|
|
293
|
+
message_types: Type definitions for the message
|
|
294
|
+
message: Message data to sign
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
Signature bytes
|
|
298
|
+
"""
|
|
299
|
+
if not self.account:
|
|
300
|
+
raise ValueError("Cannot sign message: SDK is in read-only mode. Provide a signer to enable signing.")
|
|
301
|
+
|
|
302
|
+
from eth_account.messages import encode_typed_data
|
|
303
|
+
|
|
304
|
+
structured_data = {
|
|
305
|
+
"types": {
|
|
306
|
+
"EIP712Domain": [
|
|
307
|
+
{"name": "name", "type": "string"},
|
|
308
|
+
{"name": "version", "type": "string"},
|
|
309
|
+
{"name": "chainId", "type": "uint256"},
|
|
310
|
+
{"name": "verifyingContract", "type": "address"}
|
|
311
|
+
],
|
|
312
|
+
**message_types
|
|
313
|
+
},
|
|
314
|
+
"domain": domain,
|
|
315
|
+
"primaryType": list(message_types.keys())[0] if message_types else "Message",
|
|
316
|
+
"message": message
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
encoded = encode_typed_data(full_message=structured_data)
|
|
320
|
+
signed = self.account.sign_message(encoded)
|
|
321
|
+
return signed.signature
|
|
322
|
+
|
|
323
|
+
def verifyEIP712Signature(
|
|
324
|
+
self,
|
|
325
|
+
domain: Dict[str, Any],
|
|
326
|
+
message_types: Dict[str, List[Dict[str, str]]],
|
|
327
|
+
message: Dict[str, Any],
|
|
328
|
+
signature: bytes
|
|
329
|
+
) -> str:
|
|
330
|
+
"""Verify an EIP-712 signature and recover the signer address.
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
domain: EIP-712 domain separator
|
|
334
|
+
message_types: Type definitions for the message
|
|
335
|
+
message: Message data that was signed
|
|
336
|
+
signature: Signature bytes to verify
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
Recovered signer address
|
|
340
|
+
"""
|
|
341
|
+
from eth_account.messages import encode_typed_data
|
|
342
|
+
|
|
343
|
+
structured_data = {
|
|
344
|
+
"types": {
|
|
345
|
+
"EIP712Domain": [
|
|
346
|
+
{"name": "name", "type": "string"},
|
|
347
|
+
{"name": "version", "type": "string"},
|
|
348
|
+
{"name": "chainId", "type": "uint256"},
|
|
349
|
+
{"name": "verifyingContract", "type": "address"}
|
|
350
|
+
],
|
|
351
|
+
**message_types
|
|
352
|
+
},
|
|
353
|
+
"domain": domain,
|
|
354
|
+
"primaryType": list(message_types.keys())[0] if message_types else "Message",
|
|
355
|
+
"message": message
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
encoded = encode_typed_data(full_message=structured_data)
|
|
359
|
+
return self.w3.eth.account.recover_message(encoded, signature=signature)
|