agent0-sdk 0.31__py3-none-any.whl → 1.0.0__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 +172 -30
- agent0_sdk/core/contracts.py +93 -58
- agent0_sdk/core/feedback_manager.py +90 -161
- agent0_sdk/core/indexer.py +54 -26
- agent0_sdk/core/models.py +6 -19
- agent0_sdk/core/oasf_validator.py +1 -1
- agent0_sdk/core/sdk.py +31 -16
- agent0_sdk/core/subgraph_client.py +34 -15
- agent0_sdk/core/web3_client.py +184 -17
- {agent0_sdk-0.31.dist-info → agent0_sdk-1.0.0.dist-info}/METADATA +21 -7
- agent0_sdk-1.0.0.dist-info/RECORD +19 -0
- {agent0_sdk-0.31.dist-info → agent0_sdk-1.0.0.dist-info}/top_level.txt +0 -1
- agent0_sdk-0.31.dist-info/RECORD +0 -33
- tests/__init__.py +0 -1
- tests/config.py +0 -46
- tests/conftest.py +0 -22
- tests/discover_test_data.py +0 -445
- tests/test_feedback.py +0 -417
- tests/test_models.py +0 -224
- tests/test_multi_chain.py +0 -588
- tests/test_oasf_management.py +0 -404
- 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.31.dist-info → agent0_sdk-1.0.0.dist-info}/WHEEL +0 -0
- {agent0_sdk-0.31.dist-info → agent0_sdk-1.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -40,60 +40,6 @@ class FeedbackManager:
|
|
|
40
40
|
self.subgraph_client = subgraph_client
|
|
41
41
|
self.indexer = indexer
|
|
42
42
|
|
|
43
|
-
def signFeedbackAuth(
|
|
44
|
-
self,
|
|
45
|
-
agentId: AgentId,
|
|
46
|
-
clientAddress: Address,
|
|
47
|
-
indexLimit: Optional[int] = None,
|
|
48
|
-
expiryHours: int = 24,
|
|
49
|
-
) -> bytes:
|
|
50
|
-
"""Sign feedback authorization for a client."""
|
|
51
|
-
# Parse agent ID to get token ID
|
|
52
|
-
if ":" in agentId:
|
|
53
|
-
tokenId = int(agentId.split(":")[-1])
|
|
54
|
-
else:
|
|
55
|
-
tokenId = int(agentId)
|
|
56
|
-
|
|
57
|
-
# Get current feedback index if not provided
|
|
58
|
-
if indexLimit is None:
|
|
59
|
-
try:
|
|
60
|
-
lastIndex = self.web3_client.call_contract(
|
|
61
|
-
self.reputation_registry,
|
|
62
|
-
"getLastIndex",
|
|
63
|
-
tokenId,
|
|
64
|
-
clientAddress
|
|
65
|
-
)
|
|
66
|
-
indexLimit = lastIndex + 1
|
|
67
|
-
except Exception as e:
|
|
68
|
-
# If we can't get the index, default to 1 (for first feedback)
|
|
69
|
-
indexLimit = 1
|
|
70
|
-
|
|
71
|
-
# Calculate expiry timestamp
|
|
72
|
-
expiry = int(time.time()) + (expiryHours * 3600)
|
|
73
|
-
|
|
74
|
-
# Encode feedback auth data
|
|
75
|
-
authData = self.web3_client.encodeFeedbackAuth(
|
|
76
|
-
agentId=tokenId,
|
|
77
|
-
clientAddress=clientAddress,
|
|
78
|
-
indexLimit=indexLimit,
|
|
79
|
-
expiry=expiry,
|
|
80
|
-
chainId=self.web3_client.chain_id,
|
|
81
|
-
identityRegistry=self.identity_registry.address if self.identity_registry else "0x0",
|
|
82
|
-
signerAddress=self.web3_client.account.address
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
# Hash the encoded data first (matching contract's keccak256(abi.encode(...)))
|
|
86
|
-
messageHash = self.web3_client.w3.keccak(authData)
|
|
87
|
-
|
|
88
|
-
# Sign the hash with Ethereum signed message prefix (matching contract's .toEthSignedMessageHash())
|
|
89
|
-
from eth_account.messages import encode_defunct
|
|
90
|
-
signableMessage = encode_defunct(primitive=messageHash)
|
|
91
|
-
signedMessage = self.web3_client.account.sign_message(signableMessage)
|
|
92
|
-
signature = signedMessage.signature
|
|
93
|
-
|
|
94
|
-
# Combine auth data and signature
|
|
95
|
-
return authData + signature
|
|
96
|
-
|
|
97
43
|
def prepareFeedback(
|
|
98
44
|
self,
|
|
99
45
|
agentId: AgentId,
|
|
@@ -104,6 +50,8 @@ class FeedbackManager:
|
|
|
104
50
|
name: Optional[str] = None,
|
|
105
51
|
skill: Optional[str] = None,
|
|
106
52
|
task: Optional[str] = None,
|
|
53
|
+
endpoint: Optional[str] = None,
|
|
54
|
+
domain: Optional[str] = None,
|
|
107
55
|
context: Optional[Dict[str, Any]] = None,
|
|
108
56
|
proofOfPayment: Optional[Dict[str, Any]] = None,
|
|
109
57
|
extra: Optional[Dict[str, Any]] = None,
|
|
@@ -128,12 +76,13 @@ class FeedbackManager:
|
|
|
128
76
|
"agentId": tokenId,
|
|
129
77
|
"clientAddress": f"eip155:{self.web3_client.chain_id}:{self.web3_client.account.address}",
|
|
130
78
|
"createdAt": createdAt,
|
|
131
|
-
"feedbackAuth": "", # Will be filled when giving feedback
|
|
132
79
|
"score": int(score) if score else 0, # Score as integer (0-100)
|
|
133
80
|
|
|
134
81
|
# MAY FIELDS
|
|
135
82
|
"tag1": tags[0] if tags else None,
|
|
136
83
|
"tag2": tags[1] if len(tags) > 1 else None,
|
|
84
|
+
"endpoint": endpoint,
|
|
85
|
+
"domain": domain,
|
|
137
86
|
"skill": skill,
|
|
138
87
|
"context": context,
|
|
139
88
|
"task": task,
|
|
@@ -155,7 +104,6 @@ class FeedbackManager:
|
|
|
155
104
|
agentId: AgentId,
|
|
156
105
|
feedbackFile: Dict[str, Any],
|
|
157
106
|
idem: Optional[IdemKey] = None,
|
|
158
|
-
feedbackAuth: Optional[bytes] = None,
|
|
159
107
|
) -> Feedback:
|
|
160
108
|
"""Give feedback (maps 8004 endpoint)."""
|
|
161
109
|
# Parse agent ID
|
|
@@ -180,22 +128,11 @@ class FeedbackManager:
|
|
|
180
128
|
except Exception as e:
|
|
181
129
|
raise ValueError(f"Failed to get feedback index: {e}")
|
|
182
130
|
|
|
183
|
-
# Prepare
|
|
184
|
-
if feedbackAuth is None:
|
|
185
|
-
feedbackAuth = self.sign_feedbackAuth(
|
|
186
|
-
agentId=agentId,
|
|
187
|
-
clientAddress=clientAddress,
|
|
188
|
-
indexLimit=feedbackIndex,
|
|
189
|
-
expiryHours=24
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
# Update feedback file with auth
|
|
193
|
-
feedbackFile["feedbackAuth"] = feedbackAuth.hex()
|
|
194
|
-
|
|
195
|
-
# Prepare on-chain data (only basic fields, no capability/endpoint)
|
|
131
|
+
# Prepare on-chain data
|
|
196
132
|
score = feedbackFile.get("score", 0) # Already in 0-100 range
|
|
197
|
-
tag1 =
|
|
198
|
-
tag2 =
|
|
133
|
+
tag1 = feedbackFile.get("tag1", "") or ""
|
|
134
|
+
tag2 = feedbackFile.get("tag2", "") or ""
|
|
135
|
+
endpoint = feedbackFile.get("endpoint", "") or ""
|
|
199
136
|
|
|
200
137
|
# Handle off-chain file storage
|
|
201
138
|
feedbackUri = ""
|
|
@@ -216,7 +153,7 @@ class FeedbackManager:
|
|
|
216
153
|
# If we have rich data but no IPFS, we need to store it somewhere
|
|
217
154
|
raise ValueError("Rich feedback data requires IPFS client for storage")
|
|
218
155
|
|
|
219
|
-
# Submit to blockchain
|
|
156
|
+
# Submit to blockchain with new signature: giveFeedback(agentId, score, tag1, tag2, endpoint, feedbackURI, feedbackHash)
|
|
220
157
|
try:
|
|
221
158
|
txHash = self.web3_client.transact_contract(
|
|
222
159
|
self.reputation_registry,
|
|
@@ -225,9 +162,9 @@ class FeedbackManager:
|
|
|
225
162
|
score,
|
|
226
163
|
tag1,
|
|
227
164
|
tag2,
|
|
165
|
+
endpoint,
|
|
228
166
|
feedbackUri,
|
|
229
|
-
feedbackHash
|
|
230
|
-
feedbackAuth
|
|
167
|
+
feedbackHash
|
|
231
168
|
)
|
|
232
169
|
|
|
233
170
|
# Wait for transaction confirmation
|
|
@@ -244,11 +181,12 @@ class FeedbackManager:
|
|
|
244
181
|
agentId=agentId,
|
|
245
182
|
reviewer=clientAddress, # create_id normalizes the ID; reviewer field can remain as-is
|
|
246
183
|
score=int(score) if score and score > 0 else None,
|
|
247
|
-
tags=[
|
|
184
|
+
tags=[tag1, tag2] if tag1 or tag2 else [],
|
|
248
185
|
text=feedbackFile.get("text"),
|
|
249
186
|
context=feedbackFile.get("context"),
|
|
250
187
|
proofOfPayment=feedbackFile.get("proofOfPayment"),
|
|
251
188
|
fileURI=feedbackUri if feedbackUri else None,
|
|
189
|
+
endpoint=endpoint if endpoint else None,
|
|
252
190
|
createdAt=int(time.time()),
|
|
253
191
|
isRevoked=False,
|
|
254
192
|
# Off-chain only fields
|
|
@@ -265,17 +203,21 @@ class FeedbackManager:
|
|
|
265
203
|
feedbackIndex: int,
|
|
266
204
|
) -> Feedback:
|
|
267
205
|
"""Get single feedback with responses from subgraph or blockchain."""
|
|
268
|
-
#
|
|
206
|
+
# Prefer subgraph/indexer for richer data, but fall back to chain when subgraph is behind
|
|
269
207
|
if self.indexer and self.subgraph_client:
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
208
|
+
try:
|
|
209
|
+
return self.indexer.get_feedback(agentId, clientAddress, feedbackIndex)
|
|
210
|
+
except Exception as e:
|
|
211
|
+
logger.debug(f"Indexer/subgraph get_feedback failed, falling back to blockchain: {e}")
|
|
212
|
+
return self._get_feedback_from_blockchain(agentId, clientAddress, feedbackIndex)
|
|
213
|
+
|
|
275
214
|
if self.subgraph_client:
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
215
|
+
try:
|
|
216
|
+
return self._get_feedback_from_subgraph(agentId, clientAddress, feedbackIndex)
|
|
217
|
+
except Exception as e:
|
|
218
|
+
logger.debug(f"Subgraph get feedback failed, falling back to blockchain: {e}")
|
|
219
|
+
return self._get_feedback_from_blockchain(agentId, clientAddress, feedbackIndex)
|
|
220
|
+
|
|
279
221
|
return self._get_feedback_from_blockchain(agentId, clientAddress, feedbackIndex)
|
|
280
222
|
|
|
281
223
|
def _get_feedback_from_subgraph(
|
|
@@ -320,24 +262,14 @@ class FeedbackManager:
|
|
|
320
262
|
'createdAt': resp.get('createdAt')
|
|
321
263
|
})
|
|
322
264
|
|
|
323
|
-
# Map tags
|
|
324
|
-
tags = []
|
|
265
|
+
# Map tags: rely on whatever the subgraph returns (may be legacy bytes/hash-like values)
|
|
266
|
+
tags: List[str] = []
|
|
325
267
|
tag1 = feedback_data.get('tag1') or feedback_file.get('tag1')
|
|
326
268
|
tag2 = feedback_data.get('tag2') or feedback_file.get('tag2')
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if
|
|
330
|
-
tags
|
|
331
|
-
tag1 if isinstance(tag1, str) else "",
|
|
332
|
-
tag2 if isinstance(tag2, str) else ""
|
|
333
|
-
)
|
|
334
|
-
|
|
335
|
-
# If conversion failed, try as plain strings
|
|
336
|
-
if not tags:
|
|
337
|
-
if tag1 and not tag1.startswith("0x"):
|
|
338
|
-
tags.append(tag1)
|
|
339
|
-
if tag2 and not tag2.startswith("0x"):
|
|
340
|
-
tags.append(tag2)
|
|
269
|
+
if isinstance(tag1, str) and tag1:
|
|
270
|
+
tags.append(tag1)
|
|
271
|
+
if isinstance(tag2, str) and tag2:
|
|
272
|
+
tags.append(tag2)
|
|
341
273
|
|
|
342
274
|
return Feedback(
|
|
343
275
|
id=Feedback.create_id(agentId, clientAddress, feedbackIndex), # create_id now normalizes
|
|
@@ -354,7 +286,8 @@ class FeedbackManager:
|
|
|
354
286
|
'chainId': feedback_file.get('proofOfPaymentChainId'),
|
|
355
287
|
'txHash': feedback_file.get('proofOfPaymentTxHash'),
|
|
356
288
|
} if feedback_file.get('proofOfPaymentFromAddress') else None,
|
|
357
|
-
fileURI=feedback_data.get('feedbackUri'),
|
|
289
|
+
fileURI=feedback_data.get('feedbackURI') or feedback_data.get('feedbackUri'), # Handle both old and new field names
|
|
290
|
+
endpoint=feedback_data.get('endpoint'),
|
|
358
291
|
createdAt=feedback_data.get('createdAt', int(time.time())),
|
|
359
292
|
answers=answers,
|
|
360
293
|
isRevoked=feedback_data.get('isRevoked', False),
|
|
@@ -380,7 +313,7 @@ class FeedbackManager:
|
|
|
380
313
|
tokenId = int(agentId)
|
|
381
314
|
|
|
382
315
|
try:
|
|
383
|
-
# Read from blockchain
|
|
316
|
+
# Read from blockchain - new signature: readFeedback(agentId, clientAddress, feedbackIndex)
|
|
384
317
|
result = self.web3_client.call_contract(
|
|
385
318
|
self.reputation_registry,
|
|
386
319
|
"readFeedback",
|
|
@@ -395,17 +328,25 @@ class FeedbackManager:
|
|
|
395
328
|
normalized_address = self.web3_client.normalize_address(clientAddress)
|
|
396
329
|
feedbackId = Feedback.create_id(agentId, normalized_address, feedbackIndex)
|
|
397
330
|
|
|
331
|
+
# Tags are now strings, not bytes32
|
|
332
|
+
tags = []
|
|
333
|
+
if tag1:
|
|
334
|
+
tags.append(tag1)
|
|
335
|
+
if tag2:
|
|
336
|
+
tags.append(tag2)
|
|
337
|
+
|
|
398
338
|
return Feedback(
|
|
399
339
|
id=feedbackId,
|
|
400
340
|
agentId=agentId,
|
|
401
341
|
reviewer=normalized_address,
|
|
402
342
|
score=int(score) if score and score > 0 else None,
|
|
403
|
-
tags=
|
|
343
|
+
tags=tags,
|
|
404
344
|
text=None, # Not stored on-chain
|
|
405
345
|
capability=None, # Not stored on-chain
|
|
406
346
|
context=None, # Not stored on-chain
|
|
407
347
|
proofOfPayment=None, # Not stored on-chain
|
|
408
348
|
fileURI=None, # Would need to be retrieved separately
|
|
349
|
+
endpoint=None, # Not stored on-chain in readFeedback
|
|
409
350
|
createdAt=int(time.time()), # Not stored on-chain
|
|
410
351
|
isRevoked=is_revoked
|
|
411
352
|
)
|
|
@@ -453,12 +394,12 @@ class FeedbackManager:
|
|
|
453
394
|
tokenId = int(agentId)
|
|
454
395
|
|
|
455
396
|
try:
|
|
456
|
-
# Prepare filter parameters
|
|
397
|
+
# Prepare filter parameters - tags are now strings
|
|
457
398
|
client_list = clientAddresses if clientAddresses else []
|
|
458
|
-
tag1_filter =
|
|
459
|
-
tag2_filter =
|
|
399
|
+
tag1_filter = tags[0] if tags else ""
|
|
400
|
+
tag2_filter = tags[1] if tags and len(tags) > 1 else ""
|
|
460
401
|
|
|
461
|
-
# Read from blockchain
|
|
402
|
+
# Read from blockchain - new signature returns: (clientAddresses, feedbackIndexes, scores, tag1s, tag2s, revokedStatuses)
|
|
462
403
|
result = self.web3_client.call_contract(
|
|
463
404
|
self.reputation_registry,
|
|
464
405
|
"readAllFeedback",
|
|
@@ -469,19 +410,27 @@ class FeedbackManager:
|
|
|
469
410
|
include_revoked
|
|
470
411
|
)
|
|
471
412
|
|
|
472
|
-
clients, scores, tag1s, tag2s, revoked_statuses = result
|
|
413
|
+
clients, feedback_indexes, scores, tag1s, tag2s, revoked_statuses = result
|
|
473
414
|
|
|
474
415
|
# Convert to Feedback objects
|
|
475
416
|
feedbacks = []
|
|
476
417
|
for i in range(len(clients)):
|
|
477
|
-
|
|
418
|
+
feedback_index = int(feedback_indexes[i]) if i < len(feedback_indexes) else (i + 1)
|
|
419
|
+
feedbackId = Feedback.create_id(agentId, clients[i], feedback_index)
|
|
420
|
+
|
|
421
|
+
# Tags are now strings
|
|
422
|
+
tags_list = []
|
|
423
|
+
if i < len(tag1s) and tag1s[i]:
|
|
424
|
+
tags_list.append(tag1s[i])
|
|
425
|
+
if i < len(tag2s) and tag2s[i]:
|
|
426
|
+
tags_list.append(tag2s[i])
|
|
478
427
|
|
|
479
428
|
feedback = Feedback(
|
|
480
429
|
id=feedbackId,
|
|
481
430
|
agentId=agentId,
|
|
482
431
|
reviewer=clients[i],
|
|
483
432
|
score=int(scores[i]) if scores[i] and scores[i] > 0 else None,
|
|
484
|
-
tags=
|
|
433
|
+
tags=tags_list,
|
|
485
434
|
text=None,
|
|
486
435
|
capability=None,
|
|
487
436
|
endpoint=None,
|
|
@@ -489,7 +438,7 @@ class FeedbackManager:
|
|
|
489
438
|
proofOfPayment=None,
|
|
490
439
|
fileURI=None,
|
|
491
440
|
createdAt=int(time.time()),
|
|
492
|
-
isRevoked=revoked_statuses[i]
|
|
441
|
+
isRevoked=revoked_statuses[i] if i < len(revoked_statuses) else False
|
|
493
442
|
)
|
|
494
443
|
feedbacks.append(feedback)
|
|
495
444
|
|
|
@@ -555,24 +504,14 @@ class FeedbackManager:
|
|
|
555
504
|
'createdAt': resp.get('createdAt')
|
|
556
505
|
})
|
|
557
506
|
|
|
558
|
-
# Map tags
|
|
559
|
-
tags_list = []
|
|
507
|
+
# Map tags: rely on whatever the subgraph returns (may be legacy bytes/hash-like values)
|
|
508
|
+
tags_list: List[str] = []
|
|
560
509
|
tag1 = fb_data.get('tag1') or feedback_file.get('tag1')
|
|
561
510
|
tag2 = fb_data.get('tag2') or feedback_file.get('tag2')
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
if
|
|
565
|
-
tags_list
|
|
566
|
-
tag1 if isinstance(tag1, str) else "",
|
|
567
|
-
tag2 if isinstance(tag2, str) else ""
|
|
568
|
-
)
|
|
569
|
-
|
|
570
|
-
# If conversion failed, try as plain strings
|
|
571
|
-
if not tags_list:
|
|
572
|
-
if tag1 and not tag1.startswith("0x"):
|
|
573
|
-
tags_list.append(tag1)
|
|
574
|
-
if tag2 and not tag2.startswith("0x"):
|
|
575
|
-
tags_list.append(tag2)
|
|
511
|
+
if isinstance(tag1, str) and tag1:
|
|
512
|
+
tags_list.append(tag1)
|
|
513
|
+
if isinstance(tag2, str) and tag2:
|
|
514
|
+
tags_list.append(tag2)
|
|
576
515
|
|
|
577
516
|
# Parse agentId from feedback ID
|
|
578
517
|
feedback_id = fb_data['id']
|
|
@@ -601,7 +540,8 @@ class FeedbackManager:
|
|
|
601
540
|
'chainId': feedback_file.get('proofOfPaymentChainId'),
|
|
602
541
|
'txHash': feedback_file.get('proofOfPaymentTxHash'),
|
|
603
542
|
} if feedback_file.get('proofOfPaymentFromAddress') else None,
|
|
604
|
-
fileURI=fb_data.get('feedbackUri'),
|
|
543
|
+
fileURI=fb_data.get('feedbackURI') or fb_data.get('feedbackUri'), # Handle both old and new field names
|
|
544
|
+
endpoint=fb_data.get('endpoint'),
|
|
605
545
|
createdAt=fb_data.get('createdAt', int(time.time())),
|
|
606
546
|
answers=answers,
|
|
607
547
|
isRevoked=fb_data.get('isRevoked', False),
|
|
@@ -682,7 +622,7 @@ class FeedbackManager:
|
|
|
682
622
|
tokenId,
|
|
683
623
|
clientAddress,
|
|
684
624
|
feedbackIndex,
|
|
685
|
-
responseUri,
|
|
625
|
+
responseUri, # Note: contract uses responseURI but variable name kept for compatibility
|
|
686
626
|
responseHash
|
|
687
627
|
)
|
|
688
628
|
|
|
@@ -750,16 +690,16 @@ class FeedbackManager:
|
|
|
750
690
|
|
|
751
691
|
try:
|
|
752
692
|
client_list = clientAddresses if clientAddresses else []
|
|
753
|
-
|
|
754
|
-
|
|
693
|
+
tag1_str = tag1 if tag1 else ""
|
|
694
|
+
tag2_str = tag2 if tag2 else ""
|
|
755
695
|
|
|
756
696
|
result = self.web3_client.call_contract(
|
|
757
697
|
self.reputation_registry,
|
|
758
698
|
"getSummary",
|
|
759
699
|
tokenId,
|
|
760
700
|
client_list,
|
|
761
|
-
|
|
762
|
-
|
|
701
|
+
tag1_str,
|
|
702
|
+
tag2_str
|
|
763
703
|
)
|
|
764
704
|
|
|
765
705
|
count, average_score = result
|
|
@@ -948,39 +888,28 @@ class FeedbackManager:
|
|
|
948
888
|
|
|
949
889
|
return "|".join(key_parts)
|
|
950
890
|
|
|
951
|
-
def
|
|
952
|
-
"""
|
|
953
|
-
if not text:
|
|
954
|
-
return b"\x00" * 32
|
|
955
|
-
|
|
956
|
-
# Encode as UTF-8 and pad/truncate to 32 bytes
|
|
957
|
-
encoded = text.encode('utf-8')
|
|
958
|
-
if len(encoded) > 32:
|
|
959
|
-
encoded = encoded[:32]
|
|
960
|
-
else:
|
|
961
|
-
encoded = encoded.ljust(32, b'\x00')
|
|
962
|
-
|
|
963
|
-
return encoded
|
|
964
|
-
|
|
965
|
-
def _bytes32ToTags(self, tag1: bytes, tag2: bytes) -> List[str]:
|
|
966
|
-
"""Convert bytes32 tags back to strings."""
|
|
967
|
-
tags = []
|
|
968
|
-
|
|
969
|
-
if tag1 and tag1 != b"\x00" * 32:
|
|
970
|
-
tag1_str = tag1.rstrip(b'\x00').decode('utf-8', errors='ignore')
|
|
971
|
-
if tag1_str:
|
|
972
|
-
tags.append(tag1_str)
|
|
891
|
+
def _normalizeTag(self, tag: str) -> str:
|
|
892
|
+
"""Normalize string tag (trim, validate length if needed).
|
|
973
893
|
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
894
|
+
Args:
|
|
895
|
+
tag: Tag string to normalize
|
|
896
|
+
|
|
897
|
+
Returns:
|
|
898
|
+
Normalized tag string
|
|
899
|
+
"""
|
|
900
|
+
if not tag:
|
|
901
|
+
return ""
|
|
902
|
+
# Trim whitespace
|
|
903
|
+
normalized = tag.strip()
|
|
904
|
+
# Tags are now strings with no length limit, but we can validate if needed
|
|
905
|
+
return normalized
|
|
980
906
|
|
|
981
907
|
def _hexBytes32ToTags(self, tag1: str, tag2: str) -> List[str]:
|
|
982
908
|
"""Convert hex bytes32 tags back to strings, or return plain strings as-is.
|
|
983
909
|
|
|
910
|
+
DEPRECATED: This method is kept for backward compatibility with old data
|
|
911
|
+
that may have bytes32 tags. New tags are strings and don't need conversion.
|
|
912
|
+
|
|
984
913
|
The subgraph now stores tags as human-readable strings (not hex),
|
|
985
914
|
so this method handles both formats for backwards compatibility.
|
|
986
915
|
"""
|
agent0_sdk/core/indexer.py
CHANGED
|
@@ -249,9 +249,9 @@ class AgentIndexer:
|
|
|
249
249
|
# Get basic agent data from contract
|
|
250
250
|
try:
|
|
251
251
|
if self.identity_registry:
|
|
252
|
-
|
|
252
|
+
agent_uri = self.web3_client.call_contract(
|
|
253
253
|
self.identity_registry,
|
|
254
|
-
"tokenURI",
|
|
254
|
+
"tokenURI", # ERC-721 standard function name, but represents agentURI
|
|
255
255
|
int(token_id)
|
|
256
256
|
)
|
|
257
257
|
else:
|
|
@@ -1041,29 +1041,42 @@ class AgentIndexer:
|
|
|
1041
1041
|
for resp in responses_data:
|
|
1042
1042
|
answers.append({
|
|
1043
1043
|
'responder': resp.get('responder'),
|
|
1044
|
-
'
|
|
1044
|
+
'responseURI': resp.get('responseURI') or resp.get('responseUri'), # Handle both old and new field names
|
|
1045
1045
|
'responseHash': resp.get('responseHash'),
|
|
1046
1046
|
'createdAt': resp.get('createdAt')
|
|
1047
1047
|
})
|
|
1048
1048
|
|
|
1049
|
-
# Map tags -
|
|
1049
|
+
# Map tags - tags are now strings (not bytes32)
|
|
1050
1050
|
tags = []
|
|
1051
1051
|
tag1 = feedback_data.get('tag1') or feedback_file.get('tag1')
|
|
1052
1052
|
tag2 = feedback_data.get('tag2') or feedback_file.get('tag2')
|
|
1053
1053
|
|
|
1054
|
-
#
|
|
1055
|
-
if tag1
|
|
1056
|
-
|
|
1057
|
-
tag1 if isinstance(tag1, str) else "",
|
|
1058
|
-
tag2 if isinstance(tag2, str) else ""
|
|
1059
|
-
)
|
|
1060
|
-
|
|
1061
|
-
# If conversion failed, try as plain strings
|
|
1062
|
-
if not tags:
|
|
1063
|
-
if tag1 and not tag1.startswith("0x"):
|
|
1054
|
+
# Tags are now plain strings, but handle backward compatibility with hex bytes32
|
|
1055
|
+
if tag1:
|
|
1056
|
+
if isinstance(tag1, str) and not tag1.startswith("0x"):
|
|
1064
1057
|
tags.append(tag1)
|
|
1065
|
-
|
|
1058
|
+
elif isinstance(tag1, str) and tag1.startswith("0x"):
|
|
1059
|
+
# Try to convert from hex bytes32 (old format)
|
|
1060
|
+
try:
|
|
1061
|
+
hex_bytes = bytes.fromhex(tag1[2:])
|
|
1062
|
+
tag1_str = hex_bytes.rstrip(b'\x00').decode('utf-8', errors='ignore')
|
|
1063
|
+
if tag1_str:
|
|
1064
|
+
tags.append(tag1_str)
|
|
1065
|
+
except Exception:
|
|
1066
|
+
pass # Ignore invalid hex strings
|
|
1067
|
+
|
|
1068
|
+
if tag2:
|
|
1069
|
+
if isinstance(tag2, str) and not tag2.startswith("0x"):
|
|
1066
1070
|
tags.append(tag2)
|
|
1071
|
+
elif isinstance(tag2, str) and tag2.startswith("0x"):
|
|
1072
|
+
# Try to convert from hex bytes32 (old format)
|
|
1073
|
+
try:
|
|
1074
|
+
hex_bytes = bytes.fromhex(tag2[2:])
|
|
1075
|
+
tag2_str = hex_bytes.rstrip(b'\x00').decode('utf-8', errors='ignore')
|
|
1076
|
+
if tag2_str:
|
|
1077
|
+
tags.append(tag2_str)
|
|
1078
|
+
except Exception:
|
|
1079
|
+
pass # Ignore invalid hex strings
|
|
1067
1080
|
|
|
1068
1081
|
return Feedback(
|
|
1069
1082
|
id=Feedback.create_id(agentId, clientAddress, feedbackIndex),
|
|
@@ -1080,7 +1093,8 @@ class AgentIndexer:
|
|
|
1080
1093
|
'chainId': feedback_file.get('proofOfPaymentChainId'),
|
|
1081
1094
|
'txHash': feedback_file.get('proofOfPaymentTxHash'),
|
|
1082
1095
|
} if feedback_file.get('proofOfPaymentFromAddress') else None,
|
|
1083
|
-
fileURI=feedback_data.get('feedbackUri'),
|
|
1096
|
+
fileURI=feedback_data.get('feedbackURI') or feedback_data.get('feedbackUri'), # Handle both old and new field names
|
|
1097
|
+
endpoint=feedback_data.get('endpoint'),
|
|
1084
1098
|
createdAt=feedback_data.get('createdAt', int(time.time())),
|
|
1085
1099
|
answers=answers,
|
|
1086
1100
|
isRevoked=feedback_data.get('isRevoked', False),
|
|
@@ -1276,10 +1290,10 @@ class AgentIndexer:
|
|
|
1276
1290
|
def _get_agent_from_blockchain(self, token_id: int, sdk) -> Optional[Dict[str, Any]]:
|
|
1277
1291
|
"""Get agent data from blockchain."""
|
|
1278
1292
|
try:
|
|
1279
|
-
# Get
|
|
1280
|
-
|
|
1293
|
+
# Get agent URI from contract (using ERC-721 tokenURI function)
|
|
1294
|
+
agent_uri = self.web3_client.call_contract(
|
|
1281
1295
|
sdk.identity_registry,
|
|
1282
|
-
"tokenURI",
|
|
1296
|
+
"tokenURI", # ERC-721 standard function name, but represents agentURI
|
|
1283
1297
|
token_id
|
|
1284
1298
|
)
|
|
1285
1299
|
|
|
@@ -1290,27 +1304,41 @@ class AgentIndexer:
|
|
|
1290
1304
|
token_id
|
|
1291
1305
|
)
|
|
1292
1306
|
|
|
1307
|
+
# Get agentWallet using new dedicated function
|
|
1308
|
+
wallet_address = None
|
|
1309
|
+
try:
|
|
1310
|
+
wallet_address = self.web3_client.call_contract(
|
|
1311
|
+
sdk.identity_registry,
|
|
1312
|
+
"getAgentWallet",
|
|
1313
|
+
token_id
|
|
1314
|
+
)
|
|
1315
|
+
if wallet_address == "0x0000000000000000000000000000000000000000":
|
|
1316
|
+
wallet_address = None
|
|
1317
|
+
except Exception:
|
|
1318
|
+
# Fallback to registration file if getAgentWallet not available
|
|
1319
|
+
pass
|
|
1320
|
+
|
|
1293
1321
|
# Create agent ID
|
|
1294
1322
|
agent_id = f"{sdk.chain_id}:{token_id}"
|
|
1295
1323
|
|
|
1296
1324
|
# Try to load registration data from IPFS
|
|
1297
|
-
registration_data = self._load_registration_from_ipfs(
|
|
1325
|
+
registration_data = self._load_registration_from_ipfs(agent_uri, sdk)
|
|
1298
1326
|
|
|
1299
1327
|
if registration_data:
|
|
1300
|
-
# Use data from IPFS
|
|
1328
|
+
# Use data from IPFS, but prefer on-chain wallet if available
|
|
1301
1329
|
return {
|
|
1302
1330
|
"agentId": agent_id,
|
|
1303
1331
|
"name": registration_data.get("name", f"Agent {token_id}"),
|
|
1304
1332
|
"description": registration_data.get("description", f"Agent registered with token ID {token_id}"),
|
|
1305
1333
|
"owner": owner,
|
|
1306
1334
|
"tokenId": token_id,
|
|
1307
|
-
"
|
|
1308
|
-
"x402support": registration_data.get("x402support", False),
|
|
1335
|
+
"agentURI": agent_uri, # Updated field name
|
|
1336
|
+
"x402support": registration_data.get("x402Support", registration_data.get("x402support", False)),
|
|
1309
1337
|
"trustModels": registration_data.get("trustModels", ["reputation"]),
|
|
1310
1338
|
"active": registration_data.get("active", True),
|
|
1311
1339
|
"endpoints": registration_data.get("endpoints", []),
|
|
1312
1340
|
"image": registration_data.get("image"),
|
|
1313
|
-
"walletAddress": registration_data.get("walletAddress"),
|
|
1341
|
+
"walletAddress": wallet_address or registration_data.get("walletAddress"), # Prefer on-chain wallet
|
|
1314
1342
|
"metadata": registration_data.get("metadata", {})
|
|
1315
1343
|
}
|
|
1316
1344
|
else:
|
|
@@ -1321,13 +1349,13 @@ class AgentIndexer:
|
|
|
1321
1349
|
"description": f"Agent registered with token ID {token_id}",
|
|
1322
1350
|
"owner": owner,
|
|
1323
1351
|
"tokenId": token_id,
|
|
1324
|
-
"
|
|
1352
|
+
"agentURI": agent_uri, # Updated field name
|
|
1325
1353
|
"x402support": False,
|
|
1326
1354
|
"trustModels": ["reputation"],
|
|
1327
1355
|
"active": True,
|
|
1328
1356
|
"endpoints": [],
|
|
1329
1357
|
"image": None,
|
|
1330
|
-
"walletAddress":
|
|
1358
|
+
"walletAddress": wallet_address,
|
|
1331
1359
|
"metadata": {}
|
|
1332
1360
|
}
|
|
1333
1361
|
except Exception as e:
|
agent0_sdk/core/models.py
CHANGED
|
@@ -88,23 +88,8 @@ class RegistrationFile:
|
|
|
88
88
|
}
|
|
89
89
|
endpoints.append(endpoint_dict)
|
|
90
90
|
|
|
91
|
-
#
|
|
92
|
-
|
|
93
|
-
# Use stored walletChainId if available, otherwise extract from agentId
|
|
94
|
-
chain_id_for_wallet = self.walletChainId
|
|
95
|
-
if chain_id_for_wallet is None:
|
|
96
|
-
# Extract chain ID from agentId if available, otherwise use 1 as default
|
|
97
|
-
chain_id_for_wallet = 1 # Default to mainnet
|
|
98
|
-
if self.agentId and ":" in self.agentId:
|
|
99
|
-
try:
|
|
100
|
-
chain_id_for_wallet = int(self.agentId.split(":")[1])
|
|
101
|
-
except (ValueError, IndexError):
|
|
102
|
-
chain_id_for_wallet = 1
|
|
103
|
-
|
|
104
|
-
endpoints.append({
|
|
105
|
-
"name": "agentWallet",
|
|
106
|
-
"endpoint": f"eip155:{chain_id_for_wallet}:{self.walletAddress}"
|
|
107
|
-
})
|
|
91
|
+
# Note: agentWallet is no longer included in endpoints array.
|
|
92
|
+
# It's now a reserved on-chain metadata key managed via setAgentWallet().
|
|
108
93
|
|
|
109
94
|
# Build registrations array
|
|
110
95
|
registrations = []
|
|
@@ -125,7 +110,7 @@ class RegistrationFile:
|
|
|
125
110
|
"registrations": registrations,
|
|
126
111
|
"supportedTrust": [tm.value if isinstance(tm, TrustModel) else tm for tm in self.trustModels],
|
|
127
112
|
"active": self.active,
|
|
128
|
-
"
|
|
113
|
+
"x402Support": self.x402support, # Use camelCase in JSON output per spec
|
|
129
114
|
"updatedAt": self.updatedAt,
|
|
130
115
|
}
|
|
131
116
|
|
|
@@ -163,7 +148,7 @@ class RegistrationFile:
|
|
|
163
148
|
endpoints=endpoints,
|
|
164
149
|
trustModels=trust_models,
|
|
165
150
|
active=data.get("active", False),
|
|
166
|
-
x402support=data.get("x402support", False),
|
|
151
|
+
x402support=data.get("x402Support", data.get("x402support", False)), # Handle both camelCase and lowercase
|
|
167
152
|
metadata=data.get("metadata", {}),
|
|
168
153
|
updatedAt=data.get("updatedAt", int(datetime.now().timestamp())),
|
|
169
154
|
)
|
|
@@ -206,6 +191,7 @@ class Feedback:
|
|
|
206
191
|
context: Optional[Dict[str, Any]] = None
|
|
207
192
|
proofOfPayment: Optional[Dict[str, Any]] = None
|
|
208
193
|
fileURI: Optional[URI] = None
|
|
194
|
+
endpoint: Optional[str] = None # Endpoint URI associated with feedback
|
|
209
195
|
createdAt: Timestamp = field(default_factory=lambda: int(datetime.now().timestamp()))
|
|
210
196
|
answers: List[Dict[str, Any]] = field(default_factory=list)
|
|
211
197
|
isRevoked: bool = False
|
|
@@ -304,6 +290,7 @@ class SearchFeedbackParams:
|
|
|
304
290
|
skills: Optional[List[str]] = None
|
|
305
291
|
tasks: Optional[List[str]] = None
|
|
306
292
|
names: Optional[List[str]] = None # MCP tool/resource/prompt names
|
|
293
|
+
endpoint: Optional[str] = None # Filter by endpoint URI
|
|
307
294
|
minScore: Optional[int] = None # 0-100
|
|
308
295
|
maxScore: Optional[int] = None # 0-100
|
|
309
296
|
includeRevoked: bool = False
|
|
@@ -64,7 +64,7 @@ def validate_skill(slug: str) -> bool:
|
|
|
64
64
|
Validate if a skill slug exists in the OASF taxonomy.
|
|
65
65
|
|
|
66
66
|
Args:
|
|
67
|
-
slug: The skill slug to validate (e.g., "natural_language_processing/summarization")
|
|
67
|
+
slug: The skill slug to validate (e.g., "natural_language_processing/natural_language_generation/summarization")
|
|
68
68
|
|
|
69
69
|
Returns:
|
|
70
70
|
True if the skill exists in the taxonomy, False otherwise
|