agent0-sdk 0.2.0__py3-none-any.whl → 0.2.2__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/endpoint_crawler.py +79 -19
- agent0_sdk/core/feedback_manager.py +7 -7
- agent0_sdk/core/indexer.py +18 -2
- agent0_sdk/core/ipfs_client.py +1 -1
- agent0_sdk/core/models.py +1 -1
- agent0_sdk/core/sdk.py +4 -4
- {agent0_sdk-0.2.0.dist-info → agent0_sdk-0.2.2.dist-info}/METADATA +6 -5
- agent0_sdk-0.2.2.dist-info/RECORD +27 -0
- tests/test_models.py +1 -1
- tests/test_sdk.py +38 -36
- tests/test_search.py +145 -1
- agent0_sdk-0.2.0.dist-info/RECORD +0 -27
- {agent0_sdk-0.2.0.dist-info → agent0_sdk-0.2.2.dist-info}/WHEEL +0 -0
- {agent0_sdk-0.2.0.dist-info → agent0_sdk-0.2.2.dist-info}/licenses/LICENSE +0 -0
- {agent0_sdk-0.2.0.dist-info → agent0_sdk-0.2.2.dist-info}/top_level.txt +0 -0
agent0_sdk/__init__.py
CHANGED
|
@@ -178,13 +178,13 @@ class EndpointCrawler:
|
|
|
178
178
|
def fetch_a2a_capabilities(self, endpoint: str) -> Optional[Dict[str, Any]]:
|
|
179
179
|
"""
|
|
180
180
|
Fetch A2A capabilities (skills) from an A2A server.
|
|
181
|
-
|
|
181
|
+
|
|
182
182
|
A2A Protocol uses agent cards to describe agent capabilities.
|
|
183
|
-
Tries multiple well-known paths: agentcard.json, .well-known/agent.json
|
|
184
|
-
|
|
183
|
+
Tries multiple well-known paths: agentcard.json, .well-known/agent.json, .well-known/agent-card.json
|
|
184
|
+
|
|
185
185
|
Args:
|
|
186
186
|
endpoint: A2A endpoint URL (must be http:// or https://)
|
|
187
|
-
|
|
187
|
+
|
|
188
188
|
Returns:
|
|
189
189
|
Dict with key: 'a2aSkills'
|
|
190
190
|
Returns None if unable to fetch
|
|
@@ -194,37 +194,97 @@ class EndpointCrawler:
|
|
|
194
194
|
if not endpoint.startswith(('http://', 'https://')):
|
|
195
195
|
logger.warning(f"A2A endpoint must be HTTP/HTTPS, got: {endpoint}")
|
|
196
196
|
return None
|
|
197
|
-
|
|
197
|
+
|
|
198
198
|
# Try multiple well-known paths for A2A agent cards
|
|
199
|
+
# Per ERC-8004, endpoint may already be full URL to agent card
|
|
200
|
+
# Per A2A spec section 5.3, recommended discovery path is /.well-known/agent-card.json
|
|
199
201
|
agentcard_urls = [
|
|
200
|
-
|
|
201
|
-
f"{endpoint}/.well-known/agent.json",
|
|
202
|
-
f"{endpoint.rstrip('/')}/.well-known/agent.json"
|
|
202
|
+
endpoint, # Try exact URL first (ERC-8004 format: full path to agent card)
|
|
203
|
+
f"{endpoint}/.well-known/agent-card.json", # Spec-recommended discovery path
|
|
204
|
+
f"{endpoint.rstrip('/')}/.well-known/agent-card.json",
|
|
205
|
+
f"{endpoint}/.well-known/agent.json", # Alternative well-known path
|
|
206
|
+
f"{endpoint.rstrip('/')}/.well-known/agent.json",
|
|
207
|
+
f"{endpoint}/agentcard.json" # Legacy path
|
|
203
208
|
]
|
|
204
|
-
|
|
209
|
+
|
|
205
210
|
for agentcard_url in agentcard_urls:
|
|
206
211
|
logger.debug(f"Attempting to fetch A2A capabilities from {agentcard_url}")
|
|
207
|
-
|
|
212
|
+
|
|
208
213
|
try:
|
|
209
214
|
response = requests.get(agentcard_url, timeout=self.timeout, allow_redirects=True)
|
|
210
|
-
|
|
215
|
+
|
|
211
216
|
if response.status_code == 200:
|
|
212
217
|
data = response.json()
|
|
213
|
-
|
|
214
|
-
# Extract
|
|
215
|
-
skills = self.
|
|
216
|
-
|
|
218
|
+
|
|
219
|
+
# Extract skill tags from agentcard
|
|
220
|
+
skills = self._extract_a2a_skills(data)
|
|
221
|
+
|
|
217
222
|
if skills:
|
|
218
|
-
logger.info(f"Successfully fetched A2A capabilities from {
|
|
223
|
+
logger.info(f"Successfully fetched A2A capabilities from {agentcard_url}: {len(skills)} skills")
|
|
219
224
|
return {'a2aSkills': skills}
|
|
220
|
-
except requests.exceptions.RequestException:
|
|
225
|
+
except requests.exceptions.RequestException as e:
|
|
221
226
|
# Try next URL
|
|
227
|
+
logger.debug(f"Failed to fetch from {agentcard_url}: {e}")
|
|
222
228
|
continue
|
|
223
|
-
|
|
229
|
+
|
|
224
230
|
except Exception as e:
|
|
225
231
|
logger.debug(f"Unexpected error fetching A2A capabilities from {endpoint}: {e}")
|
|
226
|
-
|
|
232
|
+
|
|
227
233
|
return None
|
|
234
|
+
|
|
235
|
+
def _extract_a2a_skills(self, data: Dict[str, Any]) -> List[str]:
|
|
236
|
+
"""
|
|
237
|
+
Extract skill tags from A2A agent card.
|
|
238
|
+
|
|
239
|
+
Per A2A Protocol spec (v0.3.0), agent cards should have:
|
|
240
|
+
skills: AgentSkill[] where each AgentSkill has a tags[] array
|
|
241
|
+
|
|
242
|
+
This method also handles non-standard formats for backward compatibility:
|
|
243
|
+
- detailedSkills[].tags[] (custom extension)
|
|
244
|
+
- skills: ["tag1", "tag2"] (non-compliant flat array)
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
data: Agent card JSON data
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
List of unique skill tags (strings)
|
|
251
|
+
"""
|
|
252
|
+
result = []
|
|
253
|
+
|
|
254
|
+
# Try spec-compliant format first: skills[].tags[]
|
|
255
|
+
if 'skills' in data and isinstance(data['skills'], list):
|
|
256
|
+
for skill in data['skills']:
|
|
257
|
+
if isinstance(skill, dict) and 'tags' in skill:
|
|
258
|
+
# Spec-compliant: AgentSkill object with tags
|
|
259
|
+
tags = skill['tags']
|
|
260
|
+
if isinstance(tags, list):
|
|
261
|
+
for tag in tags:
|
|
262
|
+
if isinstance(tag, str):
|
|
263
|
+
result.append(tag)
|
|
264
|
+
elif isinstance(skill, str):
|
|
265
|
+
# Non-compliant: flat string array (fallback)
|
|
266
|
+
result.append(skill)
|
|
267
|
+
|
|
268
|
+
# Fallback to detailedSkills if no tags found in skills
|
|
269
|
+
# (custom extension used by some implementations)
|
|
270
|
+
if not result and 'detailedSkills' in data and isinstance(data['detailedSkills'], list):
|
|
271
|
+
for skill in data['detailedSkills']:
|
|
272
|
+
if isinstance(skill, dict) and 'tags' in skill:
|
|
273
|
+
tags = skill['tags']
|
|
274
|
+
if isinstance(tags, list):
|
|
275
|
+
for tag in tags:
|
|
276
|
+
if isinstance(tag, str):
|
|
277
|
+
result.append(tag)
|
|
278
|
+
|
|
279
|
+
# Remove duplicates while preserving order
|
|
280
|
+
seen = set()
|
|
281
|
+
unique_result = []
|
|
282
|
+
for item in result:
|
|
283
|
+
if item not in seen:
|
|
284
|
+
seen.add(item)
|
|
285
|
+
unique_result.append(item)
|
|
286
|
+
|
|
287
|
+
return unique_result
|
|
228
288
|
|
|
229
289
|
def _extract_list(self, data: Dict[str, Any], key: str) -> List[str]:
|
|
230
290
|
"""
|
|
@@ -105,7 +105,7 @@ class FeedbackManager:
|
|
|
105
105
|
skill: Optional[str] = None,
|
|
106
106
|
task: Optional[str] = None,
|
|
107
107
|
context: Optional[Dict[str, Any]] = None,
|
|
108
|
-
|
|
108
|
+
proofOfPayment: Optional[Dict[str, Any]] = None,
|
|
109
109
|
extra: Optional[Dict[str, Any]] = None,
|
|
110
110
|
) -> Dict[str, Any]:
|
|
111
111
|
"""Prepare feedback file (local file/object) according to spec."""
|
|
@@ -139,7 +139,7 @@ class FeedbackManager:
|
|
|
139
139
|
"task": task,
|
|
140
140
|
"capability": capability,
|
|
141
141
|
"name": name,
|
|
142
|
-
"
|
|
142
|
+
"proofOfPayment": proofOfPayment,
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
# Remove None values to keep the structure clean
|
|
@@ -247,7 +247,7 @@ class FeedbackManager:
|
|
|
247
247
|
tags=[feedbackFile.get("tag1"), feedbackFile.get("tag2")] if feedbackFile.get("tag1") else [],
|
|
248
248
|
text=feedbackFile.get("text"),
|
|
249
249
|
context=feedbackFile.get("context"),
|
|
250
|
-
|
|
250
|
+
proofOfPayment=feedbackFile.get("proofOfPayment"),
|
|
251
251
|
fileURI=feedbackUri if feedbackUri else None,
|
|
252
252
|
createdAt=int(time.time()),
|
|
253
253
|
isRevoked=False,
|
|
@@ -348,7 +348,7 @@ class FeedbackManager:
|
|
|
348
348
|
text=feedback_file.get('text'),
|
|
349
349
|
capability=feedback_file.get('capability'),
|
|
350
350
|
context=feedback_file.get('context'),
|
|
351
|
-
|
|
351
|
+
proofOfPayment={
|
|
352
352
|
'fromAddress': feedback_file.get('proofOfPaymentFromAddress'),
|
|
353
353
|
'toAddress': feedback_file.get('proofOfPaymentToAddress'),
|
|
354
354
|
'chainId': feedback_file.get('proofOfPaymentChainId'),
|
|
@@ -404,7 +404,7 @@ class FeedbackManager:
|
|
|
404
404
|
text=None, # Not stored on-chain
|
|
405
405
|
capability=None, # Not stored on-chain
|
|
406
406
|
context=None, # Not stored on-chain
|
|
407
|
-
|
|
407
|
+
proofOfPayment=None, # Not stored on-chain
|
|
408
408
|
fileURI=None, # Would need to be retrieved separately
|
|
409
409
|
createdAt=int(time.time()), # Not stored on-chain
|
|
410
410
|
isRevoked=is_revoked
|
|
@@ -486,7 +486,7 @@ class FeedbackManager:
|
|
|
486
486
|
capability=None,
|
|
487
487
|
endpoint=None,
|
|
488
488
|
context=None,
|
|
489
|
-
|
|
489
|
+
proofOfPayment=None,
|
|
490
490
|
fileURI=None,
|
|
491
491
|
createdAt=int(time.time()),
|
|
492
492
|
isRevoked=revoked_statuses[i]
|
|
@@ -595,7 +595,7 @@ class FeedbackManager:
|
|
|
595
595
|
text=feedback_file.get('text'),
|
|
596
596
|
capability=feedback_file.get('capability'),
|
|
597
597
|
context=feedback_file.get('context'),
|
|
598
|
-
|
|
598
|
+
proofOfPayment={
|
|
599
599
|
'fromAddress': feedback_file.get('proofOfPaymentFromAddress'),
|
|
600
600
|
'toAddress': feedback_file.get('proofOfPaymentToAddress'),
|
|
601
601
|
'chainId': feedback_file.get('proofOfPaymentChainId'),
|
agent0_sdk/core/indexer.py
CHANGED
|
@@ -459,9 +459,25 @@ class AgentIndexer:
|
|
|
459
459
|
reg_file_where["did"] = params.did
|
|
460
460
|
if params.walletAddress is not None:
|
|
461
461
|
reg_file_where["agentWallet"] = params.walletAddress
|
|
462
|
-
|
|
462
|
+
|
|
463
463
|
if reg_file_where:
|
|
464
464
|
where_clause["registrationFile_"] = reg_file_where
|
|
465
|
+
|
|
466
|
+
# Owner filtering
|
|
467
|
+
if params.owners is not None and len(params.owners) > 0:
|
|
468
|
+
# Normalize addresses to lowercase for case-insensitive matching
|
|
469
|
+
normalized_owners = [owner.lower() for owner in params.owners]
|
|
470
|
+
if len(normalized_owners) == 1:
|
|
471
|
+
where_clause["owner"] = normalized_owners[0]
|
|
472
|
+
else:
|
|
473
|
+
where_clause["owner_in"] = normalized_owners
|
|
474
|
+
|
|
475
|
+
# Operator filtering
|
|
476
|
+
if params.operators is not None and len(params.operators) > 0:
|
|
477
|
+
# Normalize addresses to lowercase for case-insensitive matching
|
|
478
|
+
normalized_operators = [op.lower() for op in params.operators]
|
|
479
|
+
# For operators (array field), use contains to check if any operator matches
|
|
480
|
+
where_clause["operators_contains"] = normalized_operators
|
|
465
481
|
|
|
466
482
|
# Calculate pagination
|
|
467
483
|
skip = 0
|
|
@@ -719,7 +735,7 @@ class AgentIndexer:
|
|
|
719
735
|
text=feedback_file.get('text'),
|
|
720
736
|
capability=feedback_file.get('capability'),
|
|
721
737
|
context=feedback_file.get('context'),
|
|
722
|
-
|
|
738
|
+
proofOfPayment={
|
|
723
739
|
'fromAddress': feedback_file.get('proofOfPaymentFromAddress'),
|
|
724
740
|
'toAddress': feedback_file.get('proofOfPaymentToAddress'),
|
|
725
741
|
'chainId': feedback_file.get('proofOfPaymentChainId'),
|
agent0_sdk/core/ipfs_client.py
CHANGED
|
@@ -80,7 +80,7 @@ class IPFSClient:
|
|
|
80
80
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
81
81
|
raise RuntimeError(
|
|
82
82
|
"filecoin-pin CLI not found. "
|
|
83
|
-
"Install it from: https://github.com/filecoin-
|
|
83
|
+
"Install it from: https://github.com/filecoin-project/filecoin-pin?tab=readme-ov-file#cli"
|
|
84
84
|
)
|
|
85
85
|
|
|
86
86
|
def _pin_to_filecoin(self, file_path: str) -> str:
|
agent0_sdk/core/models.py
CHANGED
|
@@ -203,7 +203,7 @@ class Feedback:
|
|
|
203
203
|
tags: List[str] = field(default_factory=list)
|
|
204
204
|
text: Optional[str] = None
|
|
205
205
|
context: Optional[Dict[str, Any]] = None
|
|
206
|
-
|
|
206
|
+
proofOfPayment: Optional[Dict[str, Any]] = None
|
|
207
207
|
fileURI: Optional[URI] = None
|
|
208
208
|
createdAt: Timestamp = field(default_factory=lambda: int(datetime.now().timestamp()))
|
|
209
209
|
answers: List[Dict[str, Any]] = field(default_factory=list)
|
agent0_sdk/core/sdk.py
CHANGED
|
@@ -481,7 +481,7 @@ class SDK:
|
|
|
481
481
|
skill: Optional[str] = None,
|
|
482
482
|
task: Optional[str] = None,
|
|
483
483
|
context: Optional[Dict[str, Any]] = None,
|
|
484
|
-
|
|
484
|
+
proofOfPayment: Optional[Dict[str, Any]] = None,
|
|
485
485
|
extra: Optional[Dict[str, Any]] = None,
|
|
486
486
|
) -> Dict[str, Any]:
|
|
487
487
|
"""Prepare feedback file (local file/object)."""
|
|
@@ -495,7 +495,7 @@ class SDK:
|
|
|
495
495
|
skill=skill,
|
|
496
496
|
task=task,
|
|
497
497
|
context=context,
|
|
498
|
-
|
|
498
|
+
proofOfPayment=proofOfPayment,
|
|
499
499
|
extra=extra
|
|
500
500
|
)
|
|
501
501
|
|
|
@@ -688,12 +688,12 @@ class SDK:
|
|
|
688
688
|
skill: Optional[str] = None,
|
|
689
689
|
task: Optional[str] = None,
|
|
690
690
|
context: Optional[Dict[str, Any]] = None,
|
|
691
|
-
|
|
691
|
+
proofOfPayment: Optional[Dict[str, Any]] = None,
|
|
692
692
|
extra: Optional[Dict[str, Any]] = None,
|
|
693
693
|
) -> Dict[str, Any]:
|
|
694
694
|
"""Prepare feedback file (local file/object) according to spec."""
|
|
695
695
|
return self.feedback_manager.prepareFeedback(
|
|
696
|
-
agentId, score, tags, text, capability, name, skill, task, context,
|
|
696
|
+
agentId, score, tags, text, capability, name, skill, task, context, proofOfPayment, extra
|
|
697
697
|
)
|
|
698
698
|
|
|
699
699
|
def giveFeedback(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent0-sdk
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Python SDK for agent portability, discovery and trust based on ERC-8004
|
|
5
5
|
Author-email: Marco De Rossi <marco.derossi@consensys.net>
|
|
6
6
|
License: MIT License
|
|
@@ -47,13 +47,12 @@ Requires-Dist: eth-account>=0.8.0
|
|
|
47
47
|
Requires-Dist: requests>=2.28.0
|
|
48
48
|
Requires-Dist: pydantic>=2.0.0
|
|
49
49
|
Requires-Dist: ipfshttpclient>=0.8.0a2
|
|
50
|
-
Requires-Dist: numpy>=1.21.0
|
|
51
|
-
Requires-Dist: scikit-learn>=1.0.0
|
|
52
|
-
Requires-Dist: sentence-transformers>=2.2.0
|
|
53
50
|
Requires-Dist: aiohttp>=3.8.0
|
|
54
51
|
Requires-Dist: asyncio-throttle>=1.0.0
|
|
55
52
|
Requires-Dist: python-dotenv>=1.0.0
|
|
56
53
|
Requires-Dist: typing-extensions>=4.0.0
|
|
54
|
+
Provides-Extra: indexer
|
|
55
|
+
Requires-Dist: sentence-transformers>=2.2.0; extra == "indexer"
|
|
57
56
|
Provides-Extra: dev
|
|
58
57
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
59
58
|
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
@@ -305,4 +304,6 @@ Full documentation is available at [sdk.ag0.xyz](https://sdk.ag0.xyz), including
|
|
|
305
304
|
|
|
306
305
|
## License
|
|
307
306
|
|
|
308
|
-
|
|
307
|
+
Agent0 SDK is MIT-licensed public good brought to you by Marco De Rossi in collaboration with Consensys, 🦊 MetaMask and Agent0, Inc. We are looking for co-maintainers. Please reach out if you want to help.
|
|
308
|
+
|
|
309
|
+
Thanks also to Edge & Node (The Graph), Protocol Labs and Pinata for their support.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
agent0_sdk/__init__.py,sha256=SOJFcN81--KPhBSpHV0T85Q7Z1qZxxjLedZDCSbfkm0,919
|
|
2
|
+
agent0_sdk/core/agent.py,sha256=RQND8F3Hmmpnt-PqCXBP7T8KfrjTXo0_X2nZoMJh01w,33343
|
|
3
|
+
agent0_sdk/core/contracts.py,sha256=mER1pSae4-fiGBwaVTi7oqJ_QYaDnrtyIF7g6dkdE-0,19889
|
|
4
|
+
agent0_sdk/core/endpoint_crawler.py,sha256=QBkFc3tBSQqHj6PtSTZ5D3_HVB00KJZJdxE3uYpI9po,13611
|
|
5
|
+
agent0_sdk/core/feedback_manager.py,sha256=wkV-P5wgDwOLwhaf22qOl-3S2F_PNI7oCHBBZMAxugI,36017
|
|
6
|
+
agent0_sdk/core/indexer.py,sha256=bl5TqTSytt_qQyGqUsqZvrnXFlmQEuOgm6uKEE-u-BQ,41161
|
|
7
|
+
agent0_sdk/core/ipfs_client.py,sha256=fml1ai1BdBkgb95xAkf-ft8QsahV1HL30hBYRz7rQwI,13929
|
|
8
|
+
agent0_sdk/core/models.py,sha256=bQARVmKETiVL5uJpHxXGN2wY2GFrdzxhPHqjzL8TBlQ,12373
|
|
9
|
+
agent0_sdk/core/sdk.py,sha256=stOurnsSoedUFKaHKhrjaM1sghBzjighizV6AaZD7-U,31560
|
|
10
|
+
agent0_sdk/core/subgraph_client.py,sha256=XXFVFAoHcgEfqxc2w2OksSp8vtbKMtsNIuNbNcNQOzE,27280
|
|
11
|
+
agent0_sdk/core/web3_client.py,sha256=859ntu5dAmNlcJ3YM1w_VV2gI3mpCC9QEr-GN1236zU,6850
|
|
12
|
+
agent0_sdk-0.2.2.dist-info/licenses/LICENSE,sha256=rhZZbZm_Ovz4Oa9LNQ-ms8a1tA36wWh90ZkC0OR7WMw,1072
|
|
13
|
+
tests/__init__.py,sha256=60ffheccPhuMCtwiiKP1X-CJJXKpxJ_Ywa0aXGHR9bY,23
|
|
14
|
+
tests/config.py,sha256=1uePvkLBNubOQsvYkQSno0m007PMD1VACgm33fCYY6s,1429
|
|
15
|
+
tests/conftest.py,sha256=P-HCtVVYwSvscuaJqhrgZcv39XXNnr932ekEamzIqis,589
|
|
16
|
+
tests/test_feedback.py,sha256=7lszWYSmseJE0I4BhKzZdBiIzf2bgpPqZTZvhRrCTjY,14638
|
|
17
|
+
tests/test_models.py,sha256=kCZdoPasIIcOjEw7ToPldqARdbGVK8v8byOhFwVo7OI,7115
|
|
18
|
+
tests/test_real_public_servers.py,sha256=pCo4aLSCG9qv4D6T7jbyVmP1gt3r1jWxdes6z5XSNhU,3433
|
|
19
|
+
tests/test_registration.py,sha256=pYanDPLAFETIfabBUvO34ZDmyD0Rbcv8vecSfgSrQ70,9542
|
|
20
|
+
tests/test_registrationIpfs.py,sha256=9O3IBiN2CVMKzB19bqb-jN-nhqsN22kQINMpe9THqiI,8400
|
|
21
|
+
tests/test_sdk.py,sha256=dALLFm_A6aXx0ec-CNOLGQoadaSPZ08EEeCS6Tgnm0M,9362
|
|
22
|
+
tests/test_search.py,sha256=SiUio8H-M6Za8OXD_h9tUZdln0ayzkPJ3doTrkHv-Fs,18382
|
|
23
|
+
tests/test_transfer.py,sha256=zRBllpoMs6NhagAmaZWmD4ckbYjSvsSUerBK4oS-HlA,9258
|
|
24
|
+
agent0_sdk-0.2.2.dist-info/METADATA,sha256=mw1WxxAWC29tQ-FhDD_c7NJWPwyEGSYJ_w6atYM8CtY,11404
|
|
25
|
+
agent0_sdk-0.2.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
26
|
+
agent0_sdk-0.2.2.dist-info/top_level.txt,sha256=rgGBfOJlLi1zInQ85jBL2MpDu_ZJNbPjIGz-3Vn5rZs,17
|
|
27
|
+
agent0_sdk-0.2.2.dist-info/RECORD,,
|
tests/test_models.py
CHANGED
|
@@ -67,7 +67,7 @@ class TestRegistrationFile:
|
|
|
67
67
|
assert rf.name == "Test Agent"
|
|
68
68
|
assert rf.description == "A test agent"
|
|
69
69
|
assert rf.image == "https://example.com/image.png"
|
|
70
|
-
assert rf.active is
|
|
70
|
+
assert rf.active is False
|
|
71
71
|
assert rf.x402support is False
|
|
72
72
|
assert rf.endpoints == []
|
|
73
73
|
assert rf.trustModels == []
|
tests/test_sdk.py
CHANGED
|
@@ -18,13 +18,13 @@ class TestSDK:
|
|
|
18
18
|
mock_web3.return_value.chain_id = 11155111
|
|
19
19
|
|
|
20
20
|
sdk = SDK(
|
|
21
|
-
|
|
21
|
+
chainId=11155111,
|
|
22
22
|
signer="0x1234567890abcdef",
|
|
23
|
-
|
|
23
|
+
rpcUrl="https://eth-sepolia.g.alchemy.com/v2/test"
|
|
24
24
|
)
|
|
25
25
|
|
|
26
|
-
assert sdk.
|
|
27
|
-
assert sdk.
|
|
26
|
+
assert sdk.chainId == 11155111
|
|
27
|
+
assert sdk.rpcUrl == "https://eth-sepolia.g.alchemy.com/v2/test"
|
|
28
28
|
mock_web3.assert_called_once()
|
|
29
29
|
|
|
30
30
|
def test_create_agent(self):
|
|
@@ -33,9 +33,9 @@ class TestSDK:
|
|
|
33
33
|
mock_web3.return_value.chain_id = 11155111
|
|
34
34
|
|
|
35
35
|
sdk = SDK(
|
|
36
|
-
|
|
36
|
+
chainId=11155111,
|
|
37
37
|
signer="0x1234567890abcdef",
|
|
38
|
-
|
|
38
|
+
rpcUrl="https://eth-sepolia.g.alchemy.com/v2/test"
|
|
39
39
|
)
|
|
40
40
|
|
|
41
41
|
agent = sdk.createAgent(
|
|
@@ -55,9 +55,9 @@ class TestSDK:
|
|
|
55
55
|
mock_web3.return_value.chain_id = 11155111
|
|
56
56
|
|
|
57
57
|
sdk = SDK(
|
|
58
|
-
|
|
58
|
+
chainId=11155111,
|
|
59
59
|
signer="0x1234567890abcdef",
|
|
60
|
-
|
|
60
|
+
rpcUrl="https://eth-sepolia.g.alchemy.com/v2/test"
|
|
61
61
|
)
|
|
62
62
|
|
|
63
63
|
registries = sdk.registries()
|
|
@@ -71,13 +71,13 @@ class TestSDK:
|
|
|
71
71
|
mock_web3.return_value.chain_id = 11155111
|
|
72
72
|
|
|
73
73
|
sdk = SDK(
|
|
74
|
-
|
|
74
|
+
chainId=11155111,
|
|
75
75
|
signer="0x1234567890abcdef",
|
|
76
|
-
|
|
76
|
+
rpcUrl="https://eth-sepolia.g.alchemy.com/v2/test"
|
|
77
77
|
)
|
|
78
78
|
|
|
79
79
|
sdk.set_chain(84532) # Switch to Base Sepolia
|
|
80
|
-
assert sdk.
|
|
80
|
+
assert sdk.chainId == 84532
|
|
81
81
|
assert sdk._registries["IDENTITY"] is not None # Should have Base Sepolia registry
|
|
82
82
|
|
|
83
83
|
|
|
@@ -90,9 +90,9 @@ class TestAgent:
|
|
|
90
90
|
mock_web3.return_value.chain_id = 11155111
|
|
91
91
|
|
|
92
92
|
sdk = SDK(
|
|
93
|
-
|
|
93
|
+
chainId=11155111,
|
|
94
94
|
signer="0x1234567890abcdef",
|
|
95
|
-
|
|
95
|
+
rpcUrl="https://eth-sepolia.g.alchemy.com/v2/test"
|
|
96
96
|
)
|
|
97
97
|
|
|
98
98
|
agent = sdk.createAgent("Test Agent", "A test agent")
|
|
@@ -130,24 +130,25 @@ class TestAgent:
|
|
|
130
130
|
mock_web3.return_value.chain_id = 11155111
|
|
131
131
|
|
|
132
132
|
sdk = SDK(
|
|
133
|
-
|
|
133
|
+
chainId=11155111,
|
|
134
134
|
signer="0x1234567890abcdef",
|
|
135
|
-
|
|
135
|
+
rpcUrl="https://eth-sepolia.g.alchemy.com/v2/test"
|
|
136
136
|
)
|
|
137
137
|
|
|
138
138
|
agent = sdk.createAgent("Test Agent", "A test agent")
|
|
139
139
|
|
|
140
140
|
# Set trust models using new method
|
|
141
141
|
agent.setTrust(reputation=True, cryptoEconomic=True)
|
|
142
|
-
|
|
143
|
-
assert
|
|
144
|
-
assert TrustModel.
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
assert
|
|
150
|
-
assert
|
|
142
|
+
# Access trustModels through registration_file since property is shadowed by method
|
|
143
|
+
assert len(agent.registration_file.trustModels) == 2
|
|
144
|
+
assert TrustModel.REPUTATION in agent.registration_file.trustModels
|
|
145
|
+
assert TrustModel.CRYPTO_ECONOMIC in agent.registration_file.trustModels
|
|
146
|
+
|
|
147
|
+
# Set trust models using direct assignment (since trustModels is a property, not callable)
|
|
148
|
+
agent.registration_file.trustModels = [TrustModel.REPUTATION, "custom_trust"]
|
|
149
|
+
assert len(agent.registration_file.trustModels) == 2
|
|
150
|
+
assert TrustModel.REPUTATION in agent.registration_file.trustModels
|
|
151
|
+
assert "custom_trust" in agent.registration_file.trustModels
|
|
151
152
|
|
|
152
153
|
def test_agent_metadata_management(self):
|
|
153
154
|
"""Test agent metadata management."""
|
|
@@ -155,9 +156,9 @@ class TestAgent:
|
|
|
155
156
|
mock_web3.return_value.chain_id = 11155111
|
|
156
157
|
|
|
157
158
|
sdk = SDK(
|
|
158
|
-
|
|
159
|
+
chainId=11155111,
|
|
159
160
|
signer="0x1234567890abcdef",
|
|
160
|
-
|
|
161
|
+
rpcUrl="https://eth-sepolia.g.alchemy.com/v2/test"
|
|
161
162
|
)
|
|
162
163
|
|
|
163
164
|
agent = sdk.createAgent("Test Agent", "A test agent")
|
|
@@ -180,9 +181,9 @@ class TestAgent:
|
|
|
180
181
|
mock_web3.return_value.chain_id = 11155111
|
|
181
182
|
|
|
182
183
|
sdk = SDK(
|
|
183
|
-
|
|
184
|
+
chainId=11155111,
|
|
184
185
|
signer="0x1234567890abcdef",
|
|
185
|
-
|
|
186
|
+
rpcUrl="https://eth-sepolia.g.alchemy.com/v2/test"
|
|
186
187
|
)
|
|
187
188
|
|
|
188
189
|
agent = sdk.createAgent("Test Agent", "A test agent")
|
|
@@ -198,9 +199,10 @@ class TestAgent:
|
|
|
198
199
|
assert agent.description == "An updated agent"
|
|
199
200
|
assert agent.image == "https://example.com/new-image.png"
|
|
200
201
|
|
|
201
|
-
# Set wallet address
|
|
202
|
-
|
|
203
|
-
|
|
202
|
+
# Set wallet address (must be valid 42-character Ethereum address)
|
|
203
|
+
valid_address = "0x1234567890abcdef1234567890abcdef12345678"
|
|
204
|
+
agent.setAgentWallet(valid_address)
|
|
205
|
+
assert agent.walletAddress == valid_address
|
|
204
206
|
|
|
205
207
|
def test_agent_json_serialization(self):
|
|
206
208
|
"""Test agent JSON serialization."""
|
|
@@ -208,16 +210,16 @@ class TestAgent:
|
|
|
208
210
|
mock_web3.return_value.chain_id = 11155111
|
|
209
211
|
|
|
210
212
|
sdk = SDK(
|
|
211
|
-
|
|
213
|
+
chainId=11155111,
|
|
212
214
|
signer="0x1234567890abcdef",
|
|
213
|
-
|
|
215
|
+
rpcUrl="https://eth-sepolia.g.alchemy.com/v2/test"
|
|
214
216
|
)
|
|
215
217
|
|
|
216
218
|
agent = sdk.createAgent("Test Agent", "A test agent")
|
|
217
219
|
agent.setMCP("https://mcp.example.com/")
|
|
218
|
-
agent.
|
|
220
|
+
agent.setTrust(reputation=True)
|
|
219
221
|
|
|
220
|
-
json_data = agent.
|
|
222
|
+
json_data = agent.toJson()
|
|
221
223
|
assert isinstance(json_data, str)
|
|
222
224
|
assert "Test Agent" in json_data
|
|
223
225
|
assert "MCP" in json_data
|
|
@@ -227,7 +229,7 @@ class TestAgent:
|
|
|
227
229
|
agent.setX402Support(True)
|
|
228
230
|
assert agent.x402support is True
|
|
229
231
|
|
|
230
|
-
json_data_with_x402 = agent.
|
|
232
|
+
json_data_with_x402 = agent.toJson()
|
|
231
233
|
assert "x402support" in json_data_with_x402
|
|
232
234
|
|
|
233
235
|
# Test active status
|
tests/test_search.py
CHANGED
|
@@ -15,6 +15,13 @@ Flow:
|
|
|
15
15
|
10. Search agents by reputation with capability filtering
|
|
16
16
|
11. Advanced: Find top-rated agents with specific skills
|
|
17
17
|
12. Advanced: Find agents with multiple requirements
|
|
18
|
+
13. Pagination test
|
|
19
|
+
14. Sort by activity test
|
|
20
|
+
15. Search agents by single owner address
|
|
21
|
+
16. Search agents by multiple owner addresses
|
|
22
|
+
17. Search agents by operator addresses
|
|
23
|
+
18. Combined search: owner + active status
|
|
24
|
+
19. Combined search: owner + name filter
|
|
18
25
|
"""
|
|
19
26
|
|
|
20
27
|
import logging
|
|
@@ -261,7 +268,144 @@ def main():
|
|
|
261
268
|
print(f" {i}. ID {agent_id}: {agent['name']}")
|
|
262
269
|
except Exception as e:
|
|
263
270
|
print(f"❌ Failed activity sort: {e}")
|
|
264
|
-
|
|
271
|
+
|
|
272
|
+
print(f"\n📍 Step 15: Search Agents by Single Owner Address")
|
|
273
|
+
print("-" * 60)
|
|
274
|
+
try:
|
|
275
|
+
# First, get an agent to extract its owner address for testing
|
|
276
|
+
test_agent = sdk.getAgent(AGENT_ID)
|
|
277
|
+
if hasattr(test_agent, 'owner') and test_agent.owner:
|
|
278
|
+
owner_address = test_agent.owner
|
|
279
|
+
print(f" Testing with owner address: {owner_address}")
|
|
280
|
+
|
|
281
|
+
results = sdk.searchAgents(owners=[owner_address])
|
|
282
|
+
agents = results.get('items', [])
|
|
283
|
+
print(f"✅ Found {len(agents)} agent(s) owned by {owner_address[:10]}...{owner_address[-8:]}")
|
|
284
|
+
|
|
285
|
+
for i, agent in enumerate(agents[:3], 1):
|
|
286
|
+
agent_id = agent.get('agentId', agent.get('id', 'N/A'))
|
|
287
|
+
agent_owner = agent.get('owner', 'N/A')
|
|
288
|
+
print(f" {i}. {agent['name']} (ID: {agent_id})")
|
|
289
|
+
print(f" Owner: {agent_owner[:10]}...{agent_owner[-8:] if len(agent_owner) > 18 else agent_owner}")
|
|
290
|
+
else:
|
|
291
|
+
print("⚠️ Test agent doesn't have owner information")
|
|
292
|
+
except Exception as e:
|
|
293
|
+
print(f"❌ Failed to search by single owner: {e}")
|
|
294
|
+
|
|
295
|
+
print(f"\n📍 Step 16: Search Agents by Multiple Owner Addresses")
|
|
296
|
+
print("-" * 60)
|
|
297
|
+
try:
|
|
298
|
+
# Get multiple agents to collect different owner addresses
|
|
299
|
+
results = sdk.searchAgents(page_size=5)
|
|
300
|
+
agents = results.get('items', [])
|
|
301
|
+
owner_addresses = []
|
|
302
|
+
|
|
303
|
+
for agent in agents:
|
|
304
|
+
if agent.get('owner') and agent['owner'] not in owner_addresses:
|
|
305
|
+
owner_addresses.append(agent['owner'])
|
|
306
|
+
if len(owner_addresses) >= 2:
|
|
307
|
+
break
|
|
308
|
+
|
|
309
|
+
if len(owner_addresses) >= 2:
|
|
310
|
+
print(f" Testing with {len(owner_addresses)} owner addresses")
|
|
311
|
+
results = sdk.searchAgents(owners=owner_addresses)
|
|
312
|
+
agents = results.get('items', [])
|
|
313
|
+
print(f"✅ Found {len(agents)} agent(s) owned by multiple addresses")
|
|
314
|
+
|
|
315
|
+
for i, agent in enumerate(agents[:5], 1):
|
|
316
|
+
agent_id = agent.get('agentId', agent.get('id', 'N/A'))
|
|
317
|
+
agent_owner = agent.get('owner', 'N/A')
|
|
318
|
+
print(f" {i}. {agent['name']} (Owner: {agent_owner[:10]}...{agent_owner[-8:] if len(agent_owner) > 18 else agent_owner})")
|
|
319
|
+
else:
|
|
320
|
+
print("⚠️ Not enough different owners found for multi-owner test")
|
|
321
|
+
except Exception as e:
|
|
322
|
+
print(f"❌ Failed to search by multiple owners: {e}")
|
|
323
|
+
|
|
324
|
+
print(f"\n📍 Step 17: Search Agents by Operator Addresses")
|
|
325
|
+
print("-" * 60)
|
|
326
|
+
try:
|
|
327
|
+
# First check if any agents have operators defined
|
|
328
|
+
results = sdk.searchAgents(page_size=10)
|
|
329
|
+
agents = results.get('items', [])
|
|
330
|
+
test_operators = []
|
|
331
|
+
|
|
332
|
+
for agent in agents:
|
|
333
|
+
if agent.get('operators') and len(agent['operators']) > 0:
|
|
334
|
+
test_operators = agent['operators'][:1] # Use first operator
|
|
335
|
+
print(f" Testing with operator: {test_operators[0][:10]}...{test_operators[0][-8:]}")
|
|
336
|
+
break
|
|
337
|
+
|
|
338
|
+
if test_operators:
|
|
339
|
+
results = sdk.searchAgents(operators=test_operators)
|
|
340
|
+
found_agents = results.get('items', [])
|
|
341
|
+
print(f"✅ Found {len(found_agents)} agent(s) with specified operator")
|
|
342
|
+
|
|
343
|
+
for i, agent in enumerate(found_agents[:3], 1):
|
|
344
|
+
agent_id = agent.get('agentId', agent.get('id', 'N/A'))
|
|
345
|
+
agent_operators = agent.get('operators', [])
|
|
346
|
+
print(f" {i}. {agent['name']} (ID: {agent_id})")
|
|
347
|
+
if agent_operators:
|
|
348
|
+
print(f" Operators: {len(agent_operators)} total")
|
|
349
|
+
else:
|
|
350
|
+
print("⚠️ No agents with operators found for testing")
|
|
351
|
+
except Exception as e:
|
|
352
|
+
print(f"❌ Failed to search by operators: {e}")
|
|
353
|
+
|
|
354
|
+
print(f"\n📍 Step 18: Combined Search - Owner + Active Status")
|
|
355
|
+
print("-" * 60)
|
|
356
|
+
try:
|
|
357
|
+
# Get an owner address from an active agent
|
|
358
|
+
results = sdk.searchAgents(active=True, page_size=5)
|
|
359
|
+
agents = results.get('items', [])
|
|
360
|
+
|
|
361
|
+
if agents and agents[0].get('owner'):
|
|
362
|
+
test_owner = agents[0]['owner']
|
|
363
|
+
print(f" Testing owner {test_owner[:10]}...{test_owner[-8:]} + active=True")
|
|
364
|
+
|
|
365
|
+
results = sdk.searchAgents(owners=[test_owner], active=True)
|
|
366
|
+
found_agents = results.get('items', [])
|
|
367
|
+
print(f"✅ Found {len(found_agents)} active agent(s) owned by specified address")
|
|
368
|
+
|
|
369
|
+
for i, agent in enumerate(found_agents[:3], 1):
|
|
370
|
+
agent_id = agent.get('agentId', agent.get('id', 'N/A'))
|
|
371
|
+
print(f" {i}. {agent['name']} (ID: {agent_id})")
|
|
372
|
+
print(f" Active: {agent.get('active', False)}, Owner: {agent.get('owner', 'N/A')[:10]}...")
|
|
373
|
+
else:
|
|
374
|
+
print("⚠️ No active agents with owner information found")
|
|
375
|
+
except Exception as e:
|
|
376
|
+
print(f"❌ Failed combined owner + active search: {e}")
|
|
377
|
+
|
|
378
|
+
print(f"\n📍 Step 19: Combined Search - Owner + Name Filter")
|
|
379
|
+
print("-" * 60)
|
|
380
|
+
try:
|
|
381
|
+
# Get agents and find one with both name and owner
|
|
382
|
+
results = sdk.searchAgents(page_size=10)
|
|
383
|
+
agents = results.get('items', [])
|
|
384
|
+
|
|
385
|
+
test_agent = None
|
|
386
|
+
for agent in agents:
|
|
387
|
+
if agent.get('owner') and agent.get('name'):
|
|
388
|
+
test_agent = agent
|
|
389
|
+
break
|
|
390
|
+
|
|
391
|
+
if test_agent:
|
|
392
|
+
test_owner = test_agent['owner']
|
|
393
|
+
# Use partial name for search
|
|
394
|
+
name_part = test_agent['name'][:4] if len(test_agent['name']) > 4 else test_agent['name']
|
|
395
|
+
print(f" Testing owner filter + name contains '{name_part}'")
|
|
396
|
+
|
|
397
|
+
results = sdk.searchAgents(owners=[test_owner], name=name_part)
|
|
398
|
+
found_agents = results.get('items', [])
|
|
399
|
+
print(f"✅ Found {len(found_agents)} agent(s) matching both criteria")
|
|
400
|
+
|
|
401
|
+
for i, agent in enumerate(found_agents[:3], 1):
|
|
402
|
+
agent_id = agent.get('agentId', agent.get('id', 'N/A'))
|
|
403
|
+
print(f" {i}. {agent['name']} (ID: {agent_id})")
|
|
404
|
+
else:
|
|
405
|
+
print("⚠️ No suitable test agent found")
|
|
406
|
+
except Exception as e:
|
|
407
|
+
print(f"❌ Failed combined owner + name search: {e}")
|
|
408
|
+
|
|
265
409
|
print("\n" + "=" * 60)
|
|
266
410
|
print("✅ Search Tests Completed!")
|
|
267
411
|
print("=" * 60)
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
agent0_sdk/__init__.py,sha256=8afqO4bSRL5QBBIGA3uhvBtbELAlEXBvT3jWMKvYoEs,919
|
|
2
|
-
agent0_sdk/core/agent.py,sha256=RQND8F3Hmmpnt-PqCXBP7T8KfrjTXo0_X2nZoMJh01w,33343
|
|
3
|
-
agent0_sdk/core/contracts.py,sha256=mER1pSae4-fiGBwaVTi7oqJ_QYaDnrtyIF7g6dkdE-0,19889
|
|
4
|
-
agent0_sdk/core/endpoint_crawler.py,sha256=JFLW4-SoQlpyXsUDWeOb6h-u1ilEKNVQF0r6LI-3k68,11026
|
|
5
|
-
agent0_sdk/core/feedback_manager.py,sha256=Z--m_VKP4db0-gaizL_QSe7zKEnID-S1Zqigl2HcrxM,36035
|
|
6
|
-
agent0_sdk/core/indexer.py,sha256=04Iui7BLpp87gy7P585txKLzdRP0fJs7pAVNhvNrTVE,40330
|
|
7
|
-
agent0_sdk/core/ipfs_client.py,sha256=YHZRsgHlxl3bpkaHORunBtVMNEPWKfn_xx92Yi6l1Fk,13907
|
|
8
|
-
agent0_sdk/core/models.py,sha256=tgaixQBcMOJ8V0m_5TY4AQwnQqwGG8Fzf2fzsVmcyIQ,12375
|
|
9
|
-
agent0_sdk/core/sdk.py,sha256=4bq2ooUew39b7Ya9YTtKeCGwQAAnbXpHYUzGSjgDUKU,31570
|
|
10
|
-
agent0_sdk/core/subgraph_client.py,sha256=XXFVFAoHcgEfqxc2w2OksSp8vtbKMtsNIuNbNcNQOzE,27280
|
|
11
|
-
agent0_sdk/core/web3_client.py,sha256=859ntu5dAmNlcJ3YM1w_VV2gI3mpCC9QEr-GN1236zU,6850
|
|
12
|
-
agent0_sdk-0.2.0.dist-info/licenses/LICENSE,sha256=rhZZbZm_Ovz4Oa9LNQ-ms8a1tA36wWh90ZkC0OR7WMw,1072
|
|
13
|
-
tests/__init__.py,sha256=60ffheccPhuMCtwiiKP1X-CJJXKpxJ_Ywa0aXGHR9bY,23
|
|
14
|
-
tests/config.py,sha256=1uePvkLBNubOQsvYkQSno0m007PMD1VACgm33fCYY6s,1429
|
|
15
|
-
tests/conftest.py,sha256=P-HCtVVYwSvscuaJqhrgZcv39XXNnr932ekEamzIqis,589
|
|
16
|
-
tests/test_feedback.py,sha256=7lszWYSmseJE0I4BhKzZdBiIzf2bgpPqZTZvhRrCTjY,14638
|
|
17
|
-
tests/test_models.py,sha256=4HOtzpzm0OHyzf7rDmCvXQuYbMxRyt8AyLAMd-6uqCk,7114
|
|
18
|
-
tests/test_real_public_servers.py,sha256=pCo4aLSCG9qv4D6T7jbyVmP1gt3r1jWxdes6z5XSNhU,3433
|
|
19
|
-
tests/test_registration.py,sha256=pYanDPLAFETIfabBUvO34ZDmyD0Rbcv8vecSfgSrQ70,9542
|
|
20
|
-
tests/test_registrationIpfs.py,sha256=9O3IBiN2CVMKzB19bqb-jN-nhqsN22kQINMpe9THqiI,8400
|
|
21
|
-
tests/test_sdk.py,sha256=CRnCgA7ljyuC7L3MfV1w7dtzZk2FrWqCUX0tu5nnca0,9013
|
|
22
|
-
tests/test_search.py,sha256=YC5Zd0c99JhFoy7POwrUkb36I1Q3S9hJPIQrIZIJCwI,11850
|
|
23
|
-
tests/test_transfer.py,sha256=zRBllpoMs6NhagAmaZWmD4ckbYjSvsSUerBK4oS-HlA,9258
|
|
24
|
-
agent0_sdk-0.2.0.dist-info/METADATA,sha256=dMfSUzU9Y6gtP2gaoBcbnaoT37lm9PQLN0T_n-wA-Lo,11174
|
|
25
|
-
agent0_sdk-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
26
|
-
agent0_sdk-0.2.0.dist-info/top_level.txt,sha256=rgGBfOJlLi1zInQ85jBL2MpDu_ZJNbPjIGz-3Vn5rZs,17
|
|
27
|
-
agent0_sdk-0.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|