agent0-sdk 0.2.0__tar.gz → 0.2.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 (32) hide show
  1. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/PKG-INFO +6 -5
  2. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/README.md +3 -1
  3. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/agent0_sdk/__init__.py +1 -1
  4. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/agent0_sdk/core/endpoint_crawler.py +79 -19
  5. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/agent0_sdk/core/feedback_manager.py +7 -7
  6. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/agent0_sdk/core/indexer.py +18 -2
  7. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/agent0_sdk/core/ipfs_client.py +1 -1
  8. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/agent0_sdk/core/models.py +1 -1
  9. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/agent0_sdk/core/sdk.py +4 -4
  10. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/agent0_sdk.egg-info/PKG-INFO +6 -5
  11. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/agent0_sdk.egg-info/requires.txt +3 -3
  12. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/pyproject.toml +4 -4
  13. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/tests/test_models.py +1 -1
  14. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/tests/test_sdk.py +38 -36
  15. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/tests/test_search.py +145 -1
  16. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/LICENSE +0 -0
  17. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/agent0_sdk/core/agent.py +0 -0
  18. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/agent0_sdk/core/contracts.py +0 -0
  19. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/agent0_sdk/core/subgraph_client.py +0 -0
  20. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/agent0_sdk/core/web3_client.py +0 -0
  21. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/agent0_sdk.egg-info/SOURCES.txt +0 -0
  22. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/agent0_sdk.egg-info/dependency_links.txt +0 -0
  23. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/agent0_sdk.egg-info/top_level.txt +0 -0
  24. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/setup.cfg +0 -0
  25. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/tests/__init__.py +0 -0
  26. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/tests/config.py +0 -0
  27. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/tests/conftest.py +0 -0
  28. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/tests/test_feedback.py +0 -0
  29. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/tests/test_real_public_servers.py +0 -0
  30. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/tests/test_registration.py +0 -0
  31. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/tests/test_registrationIpfs.py +0 -0
  32. {agent0_sdk-0.2.0 → agent0_sdk-0.2.2}/tests/test_transfer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agent0-sdk
3
- Version: 0.2.0
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
- MIT License - see LICENSE file for details.
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.
@@ -234,4 +234,6 @@ Full documentation is available at [sdk.ag0.xyz](https://sdk.ag0.xyz), including
234
234
 
235
235
  ## License
236
236
 
237
- MIT License - see LICENSE file for details.
237
+ 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.
238
+
239
+ Thanks also to Edge & Node (The Graph), Protocol Labs and Pinata for their support.
@@ -30,7 +30,7 @@ except ImportError:
30
30
  Agent = None
31
31
  _sdk_available = False
32
32
 
33
- __version__ = "0.2.0"
33
+ __version__ = "0.2.2"
34
34
  __all__ = [
35
35
  "SDK",
36
36
  "Agent",
@@ -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
- f"{endpoint}/agentcard.json",
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 skills from agentcard
215
- skills = self._extract_list(data, 'skills')
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 {endpoint}")
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
- proof_of_payment: Optional[Dict[str, Any]] = None,
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
- "proof_of_payment": proof_of_payment,
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
- proof_of_payment=feedbackFile.get("proof_of_payment"),
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
- proof_of_payment={
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
- proof_of_payment=None, # Not stored on-chain
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
- proof_of_payment=None,
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
- proof_of_payment={
598
+ proofOfPayment={
599
599
  'fromAddress': feedback_file.get('proofOfPaymentFromAddress'),
600
600
  'toAddress': feedback_file.get('proofOfPaymentToAddress'),
601
601
  'chainId': feedback_file.get('proofOfPaymentChainId'),
@@ -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
- proof_of_payment={
738
+ proofOfPayment={
723
739
  'fromAddress': feedback_file.get('proofOfPaymentFromAddress'),
724
740
  'toAddress': feedback_file.get('proofOfPaymentToAddress'),
725
741
  'chainId': feedback_file.get('proofOfPaymentChainId'),
@@ -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-shipyard/filecoin-pin"
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:
@@ -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
- proof_of_payment: Optional[Dict[str, Any]] = None
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)
@@ -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
- proof_of_payment: Optional[Dict[str, Any]] = None,
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
- proof_of_payment=proof_of_payment,
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
- proof_of_payment: Optional[Dict[str, Any]] = None,
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, proof_of_payment, extra
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.0
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
- MIT License - see LICENSE file for details.
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.
@@ -3,9 +3,6 @@ eth-account>=0.8.0
3
3
  requests>=2.28.0
4
4
  pydantic>=2.0.0
5
5
  ipfshttpclient>=0.8.0a2
6
- numpy>=1.21.0
7
- scikit-learn>=1.0.0
8
- sentence-transformers>=2.2.0
9
6
  aiohttp>=3.8.0
10
7
  asyncio-throttle>=1.0.0
11
8
  python-dotenv>=1.0.0
@@ -20,6 +17,9 @@ isort>=5.10.0
20
17
  mypy>=1.0.0
21
18
  flake8>=5.0.0
22
19
 
20
+ [indexer]
21
+ sentence-transformers>=2.2.0
22
+
23
23
  [test]
24
24
  pytest>=7.0.0
25
25
  pytest-asyncio>=0.21.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "agent0-sdk"
7
- version = "0.2.0"
7
+ version = "0.2.2"
8
8
  description = "Python SDK for agent portability, discovery and trust based on ERC-8004"
9
9
  authors = [
10
10
  {name = "Marco De Rossi", email = "marco.derossi@consensys.net"}
@@ -29,9 +29,6 @@ dependencies = [
29
29
  "requests>=2.28.0",
30
30
  "pydantic>=2.0.0",
31
31
  "ipfshttpclient>=0.8.0a2",
32
- "numpy>=1.21.0",
33
- "scikit-learn>=1.0.0",
34
- "sentence-transformers>=2.2.0",
35
32
  "aiohttp>=3.8.0",
36
33
  "asyncio-throttle>=1.0.0",
37
34
  "python-dotenv>=1.0.0",
@@ -39,6 +36,9 @@ dependencies = [
39
36
  ]
40
37
 
41
38
  [project.optional-dependencies]
39
+ indexer = [
40
+ "sentence-transformers>=2.2.0",
41
+ ]
42
42
  dev = [
43
43
  "pytest>=7.0.0",
44
44
  "pytest-asyncio>=0.21.0",
@@ -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 True
70
+ assert rf.active is False
71
71
  assert rf.x402support is False
72
72
  assert rf.endpoints == []
73
73
  assert rf.trustModels == []
@@ -18,13 +18,13 @@ class TestSDK:
18
18
  mock_web3.return_value.chain_id = 11155111
19
19
 
20
20
  sdk = SDK(
21
- chain_id=11155111,
21
+ chainId=11155111,
22
22
  signer="0x1234567890abcdef",
23
- rpc_url="https://eth-sepolia.g.alchemy.com/v2/test"
23
+ rpcUrl="https://eth-sepolia.g.alchemy.com/v2/test"
24
24
  )
25
25
 
26
- assert sdk.chain_id == 11155111
27
- assert sdk.rpc_url == "https://eth-sepolia.g.alchemy.com/v2/test"
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
- chain_id=11155111,
36
+ chainId=11155111,
37
37
  signer="0x1234567890abcdef",
38
- rpc_url="https://eth-sepolia.g.alchemy.com/v2/test"
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
- chain_id=11155111,
58
+ chainId=11155111,
59
59
  signer="0x1234567890abcdef",
60
- rpc_url="https://eth-sepolia.g.alchemy.com/v2/test"
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
- chain_id=11155111,
74
+ chainId=11155111,
75
75
  signer="0x1234567890abcdef",
76
- rpc_url="https://eth-sepolia.g.alchemy.com/v2/test"
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.chain_id == 84532
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
- chain_id=11155111,
93
+ chainId=11155111,
94
94
  signer="0x1234567890abcdef",
95
- rpc_url="https://eth-sepolia.g.alchemy.com/v2/test"
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
- chain_id=11155111,
133
+ chainId=11155111,
134
134
  signer="0x1234567890abcdef",
135
- rpc_url="https://eth-sepolia.g.alchemy.com/v2/test"
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
- assert len(agent.trustModels) == 2
143
- assert TrustModel.REPUTATION in agent.trustModels
144
- assert TrustModel.CRYPTO_ECONOMIC in agent.trustModels
145
-
146
- # Set trust models using old method
147
- agent.trustModels([TrustModel.REPUTATION, "custom_trust"])
148
- assert len(agent.trustModels) == 2
149
- assert TrustModel.REPUTATION in agent.trustModels
150
- assert "custom_trust" in agent.trustModels
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
- chain_id=11155111,
159
+ chainId=11155111,
159
160
  signer="0x1234567890abcdef",
160
- rpc_url="https://eth-sepolia.g.alchemy.com/v2/test"
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
- chain_id=11155111,
184
+ chainId=11155111,
184
185
  signer="0x1234567890abcdef",
185
- rpc_url="https://eth-sepolia.g.alchemy.com/v2/test"
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
- agent.setAgentWallet("0x1234567890abcdef")
203
- assert agent.walletAddress == "0x1234567890abcdef"
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
- chain_id=11155111,
213
+ chainId=11155111,
212
214
  signer="0x1234567890abcdef",
213
- rpc_url="https://eth-sepolia.g.alchemy.com/v2/test"
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.trustModels([TrustModel.REPUTATION])
220
+ agent.setTrust(reputation=True)
219
221
 
220
- json_data = agent.to_json()
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.to_json()
232
+ json_data_with_x402 = agent.toJson()
231
233
  assert "x402support" in json_data_with_x402
232
234
 
233
235
  # Test active status
@@ -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)
File without changes
File without changes
File without changes
File without changes
File without changes