agentstr 0.1.12__py3-none-any.whl → 0.1.13__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.
- agentstr/__init__.py +24 -12
- agentstr/buyer.py +37 -23
- agentstr/buyer.pyi +1 -0
- agentstr/merchant.py +68 -59
- agentstr/merchant.pyi +1 -1
- agentstr/models.py +8 -9
- agentstr/models.pyi +1 -1
- agentstr/nostr.py +75 -78
- agentstr/nostr.pyi +15 -1
- {agentstr-0.1.12.dist-info → agentstr-0.1.13.dist-info}/METADATA +10 -7
- agentstr-0.1.13.dist-info/RECORD +15 -0
- agentstr-0.1.12.dist-info/RECORD +0 -15
- {agentstr-0.1.12.dist-info → agentstr-0.1.13.dist-info}/LICENSE +0 -0
- {agentstr-0.1.12.dist-info → agentstr-0.1.13.dist-info}/WHEEL +0 -0
- {agentstr-0.1.12.dist-info → agentstr-0.1.13.dist-info}/top_level.txt +0 -0
agentstr/__init__.py
CHANGED
@@ -2,8 +2,14 @@
|
|
2
2
|
AgentStr: Nostr extension for Agno AI agents
|
3
3
|
"""
|
4
4
|
|
5
|
-
|
5
|
+
import importlib.metadata
|
6
|
+
import logging
|
6
7
|
|
8
|
+
from nostr_sdk import ShippingCost, ShippingMethod, Timestamp # type: ignore
|
9
|
+
|
10
|
+
from agentstr.nostr import EventId, Keys, Kind, NostrClient, generate_and_save_keys
|
11
|
+
|
12
|
+
from .buyer import BuyerTools
|
7
13
|
from .merchant import MerchantTools
|
8
14
|
|
9
15
|
# Import main classes to make them available at package level
|
@@ -11,27 +17,33 @@ from .models import AgentProfile, MerchantProduct, MerchantStall, NostrProfile
|
|
11
17
|
|
12
18
|
# Import version from pyproject.toml at runtime
|
13
19
|
try:
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
20
|
+
__version__ = importlib.metadata.version("agentstr")
|
21
|
+
except importlib.metadata.PackageNotFoundError:
|
22
|
+
logging.warning("Package 'agentstr' not found. Falling back to 'unknown'.")
|
23
|
+
__version__ = "unknown"
|
24
|
+
except ImportError:
|
25
|
+
logging.warning("importlib.metadata is not available. Falling back to 'unknown'.")
|
18
26
|
__version__ = "unknown"
|
19
27
|
|
28
|
+
# Define What is Exposed at the Package Level
|
20
29
|
__all__ = [
|
30
|
+
# Merchant Tools
|
21
31
|
"MerchantTools",
|
22
32
|
"MerchantProduct",
|
23
33
|
"MerchantStall",
|
34
|
+
# Buyer Tools
|
35
|
+
"BuyerTools",
|
36
|
+
# Shipping
|
24
37
|
"ShippingCost",
|
25
38
|
"ShippingMethod",
|
26
|
-
|
27
|
-
|
28
|
-
from agentstr.nostr import EventId, Keys, NostrClient, ProductData, StallData
|
29
|
-
|
30
|
-
__all__ = [
|
39
|
+
# Nostr-related utils
|
31
40
|
"EventId",
|
32
41
|
"Keys",
|
42
|
+
"Kind",
|
33
43
|
"NostrClient",
|
34
|
-
"
|
35
|
-
"
|
44
|
+
"generate_and_save_keys",
|
45
|
+
"Timestamp",
|
46
|
+
# Models
|
36
47
|
"AgentProfile",
|
48
|
+
"NostrProfile",
|
37
49
|
]
|
agentstr/buyer.py
CHANGED
@@ -1,17 +1,24 @@
|
|
1
|
+
"""
|
2
|
+
Module implementing the BuyerTools Toolkit for Agno agents.
|
3
|
+
"""
|
4
|
+
|
1
5
|
import json
|
2
6
|
import logging
|
3
7
|
from uuid import uuid4
|
4
8
|
|
5
|
-
from
|
6
|
-
from agno.document.base import Document
|
9
|
+
from pydantic import ConfigDict
|
7
10
|
|
8
11
|
from agentstr.models import AgentProfile, NostrProfile
|
9
12
|
from agentstr.nostr import NostrClient, PublicKey
|
10
13
|
|
11
14
|
try:
|
15
|
+
from agno.agent import AgentKnowledge # type: ignore
|
16
|
+
from agno.document.base import Document
|
12
17
|
from agno.tools import Toolkit
|
13
|
-
except ImportError:
|
14
|
-
raise ImportError(
|
18
|
+
except ImportError as exc:
|
19
|
+
raise ImportError(
|
20
|
+
"`agno` not installed. Please install using `pip install agno`"
|
21
|
+
) from exc
|
15
22
|
|
16
23
|
|
17
24
|
def _map_location_to_geohash(location: str) -> str:
|
@@ -21,20 +28,22 @@ def _map_location_to_geohash(location: str) -> str:
|
|
21
28
|
TBD: Implement this function. Returning a fixed geohash for now.
|
22
29
|
|
23
30
|
Args:
|
24
|
-
location: location to map to a geohash. Can be a zip code, city,
|
31
|
+
location: location to map to a geohash. Can be a zip code, city,
|
32
|
+
state, country, or latitude and longitude.
|
25
33
|
|
26
34
|
Returns:
|
27
35
|
str: geohash of the location or empty string if location is not found
|
28
36
|
"""
|
29
37
|
if "snoqualmie" in location.lower():
|
30
38
|
return "C23Q7U36W"
|
31
|
-
|
32
|
-
|
39
|
+
|
40
|
+
return ""
|
33
41
|
|
34
42
|
|
35
43
|
class BuyerTools(Toolkit):
|
36
44
|
"""
|
37
|
-
BuyerTools is a toolkit that allows an agent to find sellers and
|
45
|
+
BuyerTools is a toolkit that allows an agent to find sellers and
|
46
|
+
transact with them over Nostr.
|
38
47
|
|
39
48
|
Sellers are downloaded from the Nostr relay and cached.
|
40
49
|
Sellers can be found by name or public key.
|
@@ -44,8 +53,6 @@ class BuyerTools(Toolkit):
|
|
44
53
|
TBD: populate the sellers locations with info from stalls.
|
45
54
|
"""
|
46
55
|
|
47
|
-
from pydantic import ConfigDict
|
48
|
-
|
49
56
|
model_config = ConfigDict(
|
50
57
|
arbitrary_types_allowed=True, extra="allow", validate_assignment=True
|
51
58
|
)
|
@@ -92,8 +99,13 @@ class BuyerTools(Toolkit):
|
|
92
99
|
|
93
100
|
Args:
|
94
101
|
product: JSON string with product to purchase
|
102
|
+
|
103
|
+
Returns:
|
104
|
+
str: JSON string with status and message
|
95
105
|
"""
|
96
|
-
return json.dumps(
|
106
|
+
return json.dumps(
|
107
|
+
{"status": "success", "message": f"Product {product} purchased"}
|
108
|
+
)
|
97
109
|
|
98
110
|
def find_seller_by_name(self, name: str) -> str:
|
99
111
|
"""Find a seller by name.
|
@@ -151,9 +163,6 @@ class BuyerTools(Toolkit):
|
|
151
163
|
# Find sellers in the same geohash
|
152
164
|
for seller in self.sellers:
|
153
165
|
if geohash in seller.get_locations():
|
154
|
-
# print(
|
155
|
-
# f"geohash {geohash} found in seller {seller.get_name()} with locations {seller.get_locations()}"
|
156
|
-
# )
|
157
166
|
sellers.add(seller)
|
158
167
|
|
159
168
|
if not sellers:
|
@@ -165,9 +174,17 @@ class BuyerTools(Toolkit):
|
|
165
174
|
response = json.dumps([seller.to_dict() for seller in sellers])
|
166
175
|
# print("find_sellers_by_location: storing response in knowledge base")
|
167
176
|
self._store_response_in_knowledge_base(response)
|
168
|
-
|
177
|
+
self.logger.info("Found %d sellers", len(sellers))
|
169
178
|
return response
|
170
179
|
|
180
|
+
def get_nostr_client(self) -> NostrClient:
|
181
|
+
"""Get the Nostr client.
|
182
|
+
|
183
|
+
Returns:
|
184
|
+
NostrClient: Nostr client
|
185
|
+
"""
|
186
|
+
return self._nostr_client
|
187
|
+
|
171
188
|
def get_profile(self) -> str:
|
172
189
|
"""Get the Nostr profile of the buyer agent.
|
173
190
|
|
@@ -204,7 +221,7 @@ class BuyerTools(Toolkit):
|
|
204
221
|
response = json.dumps([stall.as_json() for stall in stalls])
|
205
222
|
self._store_response_in_knowledge_base(response)
|
206
223
|
return response
|
207
|
-
except
|
224
|
+
except RuntimeError as e:
|
208
225
|
response = json.dumps({"status": "error", "message": str(e)})
|
209
226
|
return response
|
210
227
|
|
@@ -234,7 +251,7 @@ class BuyerTools(Toolkit):
|
|
234
251
|
response = json.dumps([product.to_dict() for product in products])
|
235
252
|
self._store_response_in_knowledge_base(response)
|
236
253
|
return response
|
237
|
-
except
|
254
|
+
except RuntimeError as e:
|
238
255
|
response = json.dumps({"status": "error", "message": str(e)})
|
239
256
|
return response
|
240
257
|
|
@@ -265,7 +282,8 @@ class BuyerTools(Toolkit):
|
|
265
282
|
def _refresh_sellers(self) -> None:
|
266
283
|
"""
|
267
284
|
Internal fucntion to retrieve a new list of sellers from the Nostr relay.
|
268
|
-
The old list is discarded and the new list only contains unique sellers
|
285
|
+
The old list is discarded and the new list only contains unique sellers
|
286
|
+
currently stored at the relay.
|
269
287
|
|
270
288
|
Returns:
|
271
289
|
List[NostrProfile]: List of Nostr profiles of all sellers.
|
@@ -274,11 +292,7 @@ class BuyerTools(Toolkit):
|
|
274
292
|
if len(sellers) == 0:
|
275
293
|
self.logger.info("No sellers found")
|
276
294
|
else:
|
277
|
-
self.logger.info(
|
278
|
-
|
279
|
-
# Print the locations of the sellers
|
280
|
-
# for seller in sellers:
|
281
|
-
# print(f"Seller {seller.get_name()} has locations {seller.get_locations()}")
|
295
|
+
self.logger.info("Found %d sellers", len(sellers))
|
282
296
|
|
283
297
|
self.sellers = sellers
|
284
298
|
|
agentstr/buyer.pyi
CHANGED
@@ -19,6 +19,7 @@ class BuyerTools(Toolkit):
|
|
19
19
|
def find_seller_by_name(self, name: str) -> str: ...
|
20
20
|
def find_seller_by_public_key(self, public_key: str) -> str: ...
|
21
21
|
def find_sellers_by_location(self, location: str) -> str: ...
|
22
|
+
def get_nostr_client(self) -> NostrClient: ...
|
22
23
|
def get_profile(self) -> str: ...
|
23
24
|
def get_relay(self) -> str: ...
|
24
25
|
def get_seller_stalls(self, public_key: str) -> str: ...
|
agentstr/merchant.py
CHANGED
@@ -1,30 +1,36 @@
|
|
1
|
+
"""
|
2
|
+
Module implementing the MerchantTools Toolkit for Agno agents.
|
3
|
+
"""
|
4
|
+
|
1
5
|
import json
|
2
6
|
import logging
|
3
7
|
import time
|
4
8
|
from typing import Any, List, Optional, Tuple, Union
|
5
9
|
|
10
|
+
from pydantic import ConfigDict
|
11
|
+
|
6
12
|
from agentstr.models import AgentProfile, MerchantProduct, MerchantStall
|
7
13
|
from agentstr.nostr import EventId, NostrClient
|
8
14
|
|
9
15
|
try:
|
10
16
|
from agno.tools import Toolkit
|
11
|
-
except ImportError:
|
12
|
-
raise ImportError(
|
13
|
-
|
14
|
-
from
|
17
|
+
except ImportError as exc:
|
18
|
+
raise ImportError(
|
19
|
+
"`agno` not installed. Please install using `pip install agno`"
|
20
|
+
) from exc
|
15
21
|
|
16
22
|
|
17
23
|
class MerchantTools(Toolkit):
|
18
24
|
"""
|
19
|
-
MerchantTools is a toolkit that allows a merchant to publish
|
25
|
+
MerchantTools is a toolkit that allows a merchant to publish
|
26
|
+
products and stalls to Nostr.
|
20
27
|
|
21
28
|
TBD:
|
22
|
-
- Better differentiation between products and stalls in the database
|
29
|
+
- Better differentiation between products and stalls in the database
|
30
|
+
and products and stalls published.
|
23
31
|
|
24
32
|
"""
|
25
33
|
|
26
|
-
from pydantic import ConfigDict
|
27
|
-
|
28
34
|
model_config = ConfigDict(
|
29
35
|
arbitrary_types_allowed=True, extra="allow", validate_assignment=True
|
30
36
|
)
|
@@ -77,6 +83,12 @@ class MerchantTools(Toolkit):
|
|
77
83
|
self.register(self.remove_product_by_name)
|
78
84
|
self.register(self.remove_stall_by_name)
|
79
85
|
|
86
|
+
def get_nostr_client(self) -> NostrClient:
|
87
|
+
"""
|
88
|
+
Get the NostrClient instance
|
89
|
+
"""
|
90
|
+
return self._nostr_client
|
91
|
+
|
80
92
|
def get_profile(self) -> str:
|
81
93
|
"""
|
82
94
|
Get the merchant profile in JSON format
|
@@ -147,8 +159,8 @@ class MerchantTools(Toolkit):
|
|
147
159
|
)
|
148
160
|
# Pause for 0.5 seconds to avoid rate limiting
|
149
161
|
time.sleep(0.5)
|
150
|
-
except
|
151
|
-
logging.error(
|
162
|
+
except RuntimeError as e:
|
163
|
+
logging.error("Unable to publish product %s. Error %s", product, e)
|
152
164
|
results.append(
|
153
165
|
{"status": "error", "message": str(e), "product_name": product.name}
|
154
166
|
)
|
@@ -159,7 +171,8 @@ class MerchantTools(Toolkit):
|
|
159
171
|
self,
|
160
172
|
) -> str:
|
161
173
|
"""
|
162
|
-
Publishes or updates to Nostr all stalls managed by the merchant and adds
|
174
|
+
Publishes or updates to Nostr all stalls managed by the merchant and adds
|
175
|
+
the corresponding EventId to the Stall DB
|
163
176
|
|
164
177
|
Returns:
|
165
178
|
str: JSON array with status of all stall publishing operations
|
@@ -186,8 +199,8 @@ class MerchantTools(Toolkit):
|
|
186
199
|
)
|
187
200
|
# Pause for 0.5 seconds to avoid rate limiting
|
188
201
|
time.sleep(0.5)
|
189
|
-
except
|
190
|
-
logging.error(
|
202
|
+
except RuntimeError as e:
|
203
|
+
logging.error("Unable to publish stall %s. Error %s", stall, e)
|
191
204
|
results.append(
|
192
205
|
{"status": "error", "message": str(e), "stall_name": stall.name}
|
193
206
|
)
|
@@ -196,7 +209,8 @@ class MerchantTools(Toolkit):
|
|
196
209
|
|
197
210
|
def publish_new_product(self, product: MerchantProduct) -> str:
|
198
211
|
"""
|
199
|
-
Publishes to Nostra new product that is not currently in the Merchant's
|
212
|
+
Publishes to Nostra new product that is not currently in the Merchant's
|
213
|
+
Product DB and adds it to the Product DB
|
200
214
|
|
201
215
|
Args:
|
202
216
|
product: MerchantProduct to be published
|
@@ -224,7 +238,7 @@ class MerchantTools(Toolkit):
|
|
224
238
|
"product_name": product.name,
|
225
239
|
}
|
226
240
|
)
|
227
|
-
except
|
241
|
+
except RuntimeError as e:
|
228
242
|
return json.dumps(
|
229
243
|
{"status": "error", "message": str(e), "product_name": product.name}
|
230
244
|
)
|
@@ -233,7 +247,8 @@ class MerchantTools(Toolkit):
|
|
233
247
|
"""
|
234
248
|
Publishes or updates to Nostra given product from the Merchant's Product DB
|
235
249
|
Args:
|
236
|
-
arguments: JSON string that may contain
|
250
|
+
arguments: JSON string that may contain
|
251
|
+
{"name": "product_name"} or just "product_name"
|
237
252
|
|
238
253
|
Returns:
|
239
254
|
str: JSON string with status of the operation
|
@@ -267,6 +282,8 @@ class MerchantTools(Toolkit):
|
|
267
282
|
event_id = self._nostr_client.publish_product(product)
|
268
283
|
# Update the product_db with the new event_id
|
269
284
|
self.product_db[i] = (product, event_id)
|
285
|
+
# Pause for 0.5 seconds to avoid rate limiting
|
286
|
+
time.sleep(0.5)
|
270
287
|
return json.dumps(
|
271
288
|
{
|
272
289
|
"status": "success",
|
@@ -274,9 +291,7 @@ class MerchantTools(Toolkit):
|
|
274
291
|
"product_name": product.name,
|
275
292
|
}
|
276
293
|
)
|
277
|
-
|
278
|
-
time.sleep(0.5)
|
279
|
-
except Exception as e:
|
294
|
+
except RuntimeError as e:
|
280
295
|
return json.dumps(
|
281
296
|
{
|
282
297
|
"status": "error",
|
@@ -376,7 +391,7 @@ class MerchantTools(Toolkit):
|
|
376
391
|
)
|
377
392
|
# Pause for 0.5 seconds to avoid rate limiting
|
378
393
|
time.sleep(0.5)
|
379
|
-
except
|
394
|
+
except RuntimeError as e:
|
380
395
|
results.append(
|
381
396
|
{
|
382
397
|
"status": "error",
|
@@ -399,7 +414,7 @@ class MerchantTools(Toolkit):
|
|
399
414
|
|
400
415
|
return json.dumps(results)
|
401
416
|
|
402
|
-
except
|
417
|
+
except RuntimeError as e:
|
403
418
|
return json.dumps(
|
404
419
|
[{"status": "error", "message": str(e), "arguments": str(arguments)}]
|
405
420
|
)
|
@@ -424,12 +439,13 @@ class MerchantTools(Toolkit):
|
|
424
439
|
self.merchant_profile.get_picture(),
|
425
440
|
)
|
426
441
|
return json.dumps(event_id.__dict__)
|
427
|
-
except
|
428
|
-
raise RuntimeError(f"Unable to publish the profile: {e}")
|
442
|
+
except RuntimeError as e:
|
443
|
+
raise RuntimeError(f"Unable to publish the profile: {e}") from e
|
429
444
|
|
430
445
|
def publish_new_stall(self, stall: MerchantStall) -> str:
|
431
446
|
"""
|
432
|
-
Publishes to Nostr a new stall that is not currently in the Merchant's
|
447
|
+
Publishes to Nostr a new stall that is not currently in the Merchant's
|
448
|
+
Stall DB and adds it to the Stall DB
|
433
449
|
|
434
450
|
Args:
|
435
451
|
stall: MerchantStall to be published
|
@@ -444,7 +460,8 @@ class MerchantTools(Toolkit):
|
|
444
460
|
raise ValueError("NostrClient not initialized")
|
445
461
|
|
446
462
|
try:
|
447
|
-
# We don't ned to convert to StallData.
|
463
|
+
# We don't ned to convert to StallData.
|
464
|
+
# nostr_client.publish_stall() accepts a MerchantStall
|
448
465
|
# stall_data = stall.to_stall_data()
|
449
466
|
# Publish using the synchronous method
|
450
467
|
event_id = self._nostr_client.publish_stall(stall)
|
@@ -457,7 +474,7 @@ class MerchantTools(Toolkit):
|
|
457
474
|
"stall_name": stall.name,
|
458
475
|
}
|
459
476
|
)
|
460
|
-
except
|
477
|
+
except RuntimeError as e:
|
461
478
|
return json.dumps(
|
462
479
|
{"status": "error", "message": str(e), "stall_name": stall.name}
|
463
480
|
)
|
@@ -514,10 +531,10 @@ class MerchantTools(Toolkit):
|
|
514
531
|
for i, (stall, _) in enumerate(self.stall_db):
|
515
532
|
if stall.name == stall_name:
|
516
533
|
try:
|
517
|
-
# We are not passing StallData to nostr_client.publish_stall() anymore
|
518
|
-
# stall_data = stall.to_stall_data()
|
519
534
|
event_id = self._nostr_client.publish_stall(stall)
|
520
535
|
self.stall_db[i] = (stall, event_id)
|
536
|
+
# Pause for 0.5 seconds to avoid rate limiting
|
537
|
+
time.sleep(0.5)
|
521
538
|
return json.dumps(
|
522
539
|
{
|
523
540
|
"status": "success",
|
@@ -525,9 +542,8 @@ class MerchantTools(Toolkit):
|
|
525
542
|
"stall_name": stall.name,
|
526
543
|
}
|
527
544
|
)
|
528
|
-
|
529
|
-
|
530
|
-
except Exception as e:
|
545
|
+
|
546
|
+
except RuntimeError as e:
|
531
547
|
return json.dumps(
|
532
548
|
[
|
533
549
|
{
|
@@ -549,7 +565,7 @@ class MerchantTools(Toolkit):
|
|
549
565
|
]
|
550
566
|
)
|
551
567
|
|
552
|
-
except
|
568
|
+
except RuntimeError as e:
|
553
569
|
return json.dumps(
|
554
570
|
[{"status": "error", "message": str(e), "stall_name": "unknown"}]
|
555
571
|
)
|
@@ -574,7 +590,9 @@ class MerchantTools(Toolkit):
|
|
574
590
|
results.append(
|
575
591
|
{
|
576
592
|
"status": "skipped",
|
577
|
-
"message":
|
593
|
+
"message": (
|
594
|
+
f"Product '{product.name}' has not been published yet"
|
595
|
+
),
|
578
596
|
"product_name": product.name,
|
579
597
|
}
|
580
598
|
)
|
@@ -597,7 +615,7 @@ class MerchantTools(Toolkit):
|
|
597
615
|
)
|
598
616
|
# Pause for 0.5 seconds to avoid rate limiting
|
599
617
|
time.sleep(0.5)
|
600
|
-
except
|
618
|
+
except RuntimeError as e:
|
601
619
|
results.append(
|
602
620
|
{"status": "error", "message": str(e), "product_name": product.name}
|
603
621
|
)
|
@@ -606,7 +624,8 @@ class MerchantTools(Toolkit):
|
|
606
624
|
|
607
625
|
def remove_all_stalls(self) -> str:
|
608
626
|
"""
|
609
|
-
Removes from Nostr all stalls from the merchant and their
|
627
|
+
Removes from Nostr all stalls from the merchant and their
|
628
|
+
corresponding products
|
610
629
|
|
611
630
|
Returns:
|
612
631
|
str: JSON array with status of all removal operations
|
@@ -631,7 +650,7 @@ class MerchantTools(Toolkit):
|
|
631
650
|
results.append(
|
632
651
|
{
|
633
652
|
"status": "skipped",
|
634
|
-
"message":
|
653
|
+
"message": "Unpublished product",
|
635
654
|
"product_name": product.name,
|
636
655
|
"stall_name": stall_name,
|
637
656
|
}
|
@@ -653,7 +672,7 @@ class MerchantTools(Toolkit):
|
|
653
672
|
"event_id": str(event_id),
|
654
673
|
}
|
655
674
|
)
|
656
|
-
except
|
675
|
+
except RuntimeError as e:
|
657
676
|
results.append(
|
658
677
|
{
|
659
678
|
"status": "error",
|
@@ -669,7 +688,7 @@ class MerchantTools(Toolkit):
|
|
669
688
|
results.append(
|
670
689
|
{
|
671
690
|
"status": "skipped",
|
672
|
-
"message": f"Stall '{stall_name}' has not been published yet",
|
691
|
+
"message": (f"Stall '{stall_name}' has not been published yet"),
|
673
692
|
"stall_name": stall_name,
|
674
693
|
}
|
675
694
|
)
|
@@ -689,7 +708,7 @@ class MerchantTools(Toolkit):
|
|
689
708
|
)
|
690
709
|
# Pause for 0.5 seconds to avoid rate limiting
|
691
710
|
time.sleep(0.5)
|
692
|
-
except
|
711
|
+
except RuntimeError as e:
|
693
712
|
results.append(
|
694
713
|
{"status": "error", "message": str(e), "stall_name": stall_name}
|
695
714
|
)
|
@@ -701,7 +720,8 @@ class MerchantTools(Toolkit):
|
|
701
720
|
Removes from Nostr a product with the given name
|
702
721
|
|
703
722
|
Args:
|
704
|
-
arguments: JSON string that may contain {"name": "product_name"}
|
723
|
+
arguments: JSON string that may contain {"name": "product_name"}
|
724
|
+
or just "product_name"
|
705
725
|
|
706
726
|
Returns:
|
707
727
|
str: JSON string with status of the operation
|
@@ -754,7 +774,7 @@ class MerchantTools(Toolkit):
|
|
754
774
|
"event_id": str(event_id),
|
755
775
|
}
|
756
776
|
)
|
757
|
-
except
|
777
|
+
except RuntimeError as e:
|
758
778
|
return json.dumps(
|
759
779
|
{"status": "error", "message": str(e), "product_name": name}
|
760
780
|
)
|
@@ -843,7 +863,7 @@ class MerchantTools(Toolkit):
|
|
843
863
|
results.append(
|
844
864
|
{
|
845
865
|
"status": "skipped",
|
846
|
-
"message":
|
866
|
+
"message": "Unpublished product",
|
847
867
|
"product_name": product.name,
|
848
868
|
"stall_name": stall_name,
|
849
869
|
}
|
@@ -866,7 +886,7 @@ class MerchantTools(Toolkit):
|
|
866
886
|
)
|
867
887
|
# Pause for 0.5 seconds to avoid rate limiting
|
868
888
|
time.sleep(0.5)
|
869
|
-
except
|
889
|
+
except RuntimeError as e:
|
870
890
|
results.append(
|
871
891
|
{
|
872
892
|
"status": "error",
|
@@ -883,7 +903,9 @@ class MerchantTools(Toolkit):
|
|
883
903
|
results.append(
|
884
904
|
{
|
885
905
|
"status": "skipped",
|
886
|
-
"message":
|
906
|
+
"message": (
|
907
|
+
f"Stall '{stall_name}' has not been published yet"
|
908
|
+
),
|
887
909
|
"stall_name": stall_name,
|
888
910
|
}
|
889
911
|
)
|
@@ -904,7 +926,7 @@ class MerchantTools(Toolkit):
|
|
904
926
|
"event_id": str(stall_event_id),
|
905
927
|
}
|
906
928
|
)
|
907
|
-
except
|
929
|
+
except RuntimeError as e:
|
908
930
|
results.append(
|
909
931
|
{
|
910
932
|
"status": "error",
|
@@ -915,20 +937,7 @@ class MerchantTools(Toolkit):
|
|
915
937
|
|
916
938
|
return json.dumps(results)
|
917
939
|
|
918
|
-
except
|
940
|
+
except RuntimeError as e:
|
919
941
|
return json.dumps(
|
920
942
|
[{"status": "error", "message": str(e), "stall_name": "unknown"}]
|
921
943
|
)
|
922
|
-
|
923
|
-
# def get_event_id(self, response: Any) -> str:
|
924
|
-
# """Convert any response to a string event ID.
|
925
|
-
|
926
|
-
# Args:
|
927
|
-
# response: Response that might contain an event ID
|
928
|
-
|
929
|
-
# Returns:
|
930
|
-
# str: String representation of event ID or empty string if None
|
931
|
-
# """
|
932
|
-
# if response is None:
|
933
|
-
# return ""
|
934
|
-
# return str(response)
|
agentstr/merchant.pyi
CHANGED
@@ -18,6 +18,7 @@ class MerchantTools(Toolkit):
|
|
18
18
|
stalls: List[MerchantStall],
|
19
19
|
products: List[MerchantProduct],
|
20
20
|
) -> None: ...
|
21
|
+
def get_nostr_client(self) -> NostrClient: ...
|
21
22
|
def get_profile(self) -> str: ...
|
22
23
|
def get_relay(self) -> str: ...
|
23
24
|
def get_products(self) -> str: ...
|
@@ -34,4 +35,3 @@ class MerchantTools(Toolkit):
|
|
34
35
|
def remove_all_stalls(self) -> str: ...
|
35
36
|
def remove_product_by_name(self, product_name: str) -> str: ...
|
36
37
|
def remove_stall_by_name(self, stall_name: str) -> str: ...
|
37
|
-
def get_event_id(self, response: Any) -> str: ...
|
agentstr/models.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import json
|
2
2
|
import logging
|
3
|
-
from typing import List
|
3
|
+
from typing import List
|
4
4
|
|
5
5
|
from nostr_sdk import (
|
6
6
|
Keys,
|
@@ -131,9 +131,6 @@ class NostrProfile(Profile):
|
|
131
131
|
def get_profile_url(self) -> str:
|
132
132
|
return self.profile_url
|
133
133
|
|
134
|
-
def get_zip_codes(self) -> List[str]:
|
135
|
-
return self.zip_codes
|
136
|
-
|
137
134
|
def to_json(self) -> str:
|
138
135
|
# Ensure super().to_json() returns a dictionary
|
139
136
|
parent_json = super().to_json()
|
@@ -287,14 +284,15 @@ class MerchantProduct(BaseModel):
|
|
287
284
|
specs=self.specs,
|
288
285
|
)
|
289
286
|
except Exception as e:
|
290
|
-
logging.error(
|
291
|
-
logging.error(
|
287
|
+
logging.error("Failed to convert to ProductData: %s", e)
|
288
|
+
logging.error("Shipping data: %s", self.shipping)
|
292
289
|
raise
|
293
290
|
|
294
291
|
def to_dict(self) -> dict:
|
295
292
|
"""
|
296
293
|
Returns a dictionary representation of the MerchantProduct.
|
297
|
-
ShippingCost class is not serializable, so we need to convert it
|
294
|
+
ShippingCost class is not serializable, so we need to convert it
|
295
|
+
to a dictionary.
|
298
296
|
|
299
297
|
Returns:
|
300
298
|
dict: dictionary representation of the MerchantProduct
|
@@ -347,8 +345,9 @@ class MerchantStall(BaseModel):
|
|
347
345
|
def to_dict(self) -> dict:
|
348
346
|
"""
|
349
347
|
Returns a dictionary representation of the MerchantStall.
|
350
|
-
ShippingMethod class is not serializable, so we need to convert
|
351
|
-
We can only access cost and id from the
|
348
|
+
ShippingMethod class is not serializable, so we need to convert
|
349
|
+
it to a dictionary. We can only access cost and id from the
|
350
|
+
ShippingMethod class. We can't access name or regions.
|
352
351
|
|
353
352
|
Returns:
|
354
353
|
dict: dictionary representation of the MerchantStall
|
agentstr/models.pyi
CHANGED
agentstr/nostr.py
CHANGED
@@ -1,19 +1,16 @@
|
|
1
|
+
"""
|
2
|
+
Core Nostr utilities for agentstr.
|
3
|
+
"""
|
4
|
+
|
5
|
+
import asyncio
|
1
6
|
import json
|
2
7
|
import logging
|
3
|
-
import traceback
|
4
8
|
from datetime import timedelta
|
5
9
|
from pathlib import Path
|
6
|
-
from typing import Dict, List, Optional
|
10
|
+
from typing import Dict, List, Optional
|
7
11
|
|
8
12
|
from agentstr.models import MerchantProduct, MerchantStall, NostrProfile
|
9
13
|
|
10
|
-
try:
|
11
|
-
import asyncio
|
12
|
-
except ImportError:
|
13
|
-
raise ImportError(
|
14
|
-
"`asyncio` not installed. Please install using `pip install asyncio`"
|
15
|
-
)
|
16
|
-
|
17
14
|
try:
|
18
15
|
from nostr_sdk import (
|
19
16
|
Alphabet,
|
@@ -29,29 +26,26 @@ try:
|
|
29
26
|
NostrSigner,
|
30
27
|
ProductData,
|
31
28
|
PublicKey,
|
32
|
-
ShippingCost,
|
33
|
-
ShippingMethod,
|
34
29
|
SingleLetterTag,
|
35
30
|
StallData,
|
36
31
|
Tag,
|
37
32
|
TagKind,
|
38
33
|
TagStandard,
|
39
|
-
Timestamp,
|
40
34
|
)
|
41
|
-
|
42
|
-
except ImportError:
|
35
|
+
except ImportError as exc:
|
43
36
|
raise ImportError(
|
44
37
|
"`nostr_sdk` not installed. Please install using `pip install nostr_sdk`"
|
45
|
-
)
|
38
|
+
) from exc
|
46
39
|
|
47
40
|
|
48
41
|
class NostrClient:
|
49
42
|
"""
|
50
|
-
NostrClient implements the set of Nostr utilities required for
|
51
|
-
like the Marketplace.
|
43
|
+
NostrClient implements the set of Nostr utilities required for
|
44
|
+
higher level functions implementations like the Marketplace.
|
52
45
|
|
53
|
-
Nostr is an asynchronous communication protocol. To hide this,
|
54
|
-
|
46
|
+
Nostr is an asynchronous communication protocol. To hide this,
|
47
|
+
NostrClient exposes synchronous functions. Users of the NostrClient
|
48
|
+
should ignore `_async_` functions which are for internal purposes only.
|
55
49
|
"""
|
56
50
|
|
57
51
|
logger = logging.getLogger("NostrClient")
|
@@ -148,7 +142,7 @@ class NostrClient:
|
|
148
142
|
try:
|
149
143
|
return asyncio.run(self._async_publish_product(product))
|
150
144
|
except Exception as e:
|
151
|
-
raise RuntimeError(f"Failed to publish product: {e}")
|
145
|
+
raise RuntimeError(f"Failed to publish product: {e}") from e
|
152
146
|
|
153
147
|
def publish_profile(self, name: str, about: str, picture: str) -> EventId:
|
154
148
|
"""
|
@@ -183,7 +177,7 @@ class NostrClient:
|
|
183
177
|
try:
|
184
178
|
return asyncio.run(self._async_publish_stall(stall))
|
185
179
|
except Exception as e:
|
186
|
-
raise RuntimeError(f"Failed to publish stall: {e}")
|
180
|
+
raise RuntimeError(f"Failed to publish stall: {e}") from e
|
187
181
|
|
188
182
|
def retrieve_products_from_seller(self, seller: PublicKey) -> List[MerchantProduct]:
|
189
183
|
"""
|
@@ -217,7 +211,7 @@ class NostrClient:
|
|
217
211
|
products.append(MerchantProduct.from_product_data(product_data))
|
218
212
|
return products
|
219
213
|
except Exception as e:
|
220
|
-
raise RuntimeError(f"Failed to retrieve products: {e}")
|
214
|
+
raise RuntimeError(f"Failed to retrieve products: {e}") from e
|
221
215
|
|
222
216
|
def retrieve_profile(self, public_key: PublicKey) -> NostrProfile:
|
223
217
|
"""
|
@@ -235,7 +229,7 @@ class NostrClient:
|
|
235
229
|
try:
|
236
230
|
return asyncio.run(self._async_retrieve_profile(public_key))
|
237
231
|
except Exception as e:
|
238
|
-
raise RuntimeError(f"Failed to retrieve profile: {e}")
|
232
|
+
raise RuntimeError(f"Failed to retrieve profile: {e}") from e
|
239
233
|
|
240
234
|
def retrieve_sellers(self) -> set[NostrProfile]:
|
241
235
|
"""
|
@@ -244,17 +238,18 @@ class NostrClient:
|
|
244
238
|
Return set may be empty if metadata can't be retrieved for any author.
|
245
239
|
|
246
240
|
Returns:
|
247
|
-
set[NostrProfile]: set of seller profiles
|
241
|
+
set[NostrProfile]: set of seller profiles
|
242
|
+
(skips authors with missing metadata)
|
248
243
|
"""
|
249
244
|
|
250
|
-
sellers: set[NostrProfile] = set()
|
245
|
+
# sellers: set[NostrProfile] = set()
|
251
246
|
|
252
247
|
# First we retrieve all stalls from the relay
|
253
248
|
|
254
249
|
try:
|
255
250
|
events = asyncio.run(self._async_retrieve_all_stalls())
|
256
251
|
except Exception as e:
|
257
|
-
raise RuntimeError(f"Failed to retrieve stalls: {e}")
|
252
|
+
raise RuntimeError(f"Failed to retrieve stalls: {e}") from e
|
258
253
|
|
259
254
|
# Now we search for unique npubs from the list of stalls
|
260
255
|
|
@@ -265,17 +260,16 @@ class NostrClient:
|
|
265
260
|
if event.kind() == Kind(30017):
|
266
261
|
# Is this event the first time we see this author?
|
267
262
|
if event.author() not in authors:
|
268
|
-
# First time we see this author.
|
263
|
+
# First time we see this author.
|
264
|
+
# Let's add the profile to the dictionary
|
269
265
|
try:
|
270
266
|
profile = asyncio.run(
|
271
267
|
self._async_retrieve_profile(event.author())
|
272
268
|
)
|
273
|
-
# Add
|
269
|
+
# Add profile to the dictionary
|
270
|
+
# associated with the author's PublicKey
|
274
271
|
authors[event.author()] = profile
|
275
|
-
except
|
276
|
-
# print(
|
277
|
-
# f"Failed to retrieve profile for {event.author().to_bech32()}: {e}"
|
278
|
-
# )
|
272
|
+
except RuntimeError:
|
279
273
|
continue
|
280
274
|
|
281
275
|
# Now we add locations from the event locations to the profile
|
@@ -287,15 +281,10 @@ class NostrClient:
|
|
287
281
|
extracted_geohash = string_repr.split("=")[1].rstrip(
|
288
282
|
")"
|
289
283
|
) # Splitting and removing the closing parenthesis
|
290
|
-
|
291
|
-
# f"Adding location {extracted_geohash} to profile {authors[event.author()].get_name()}"
|
292
|
-
# )
|
284
|
+
|
293
285
|
profile = authors[event.author()]
|
294
286
|
profile.add_location(extracted_geohash)
|
295
287
|
authors[event.author()] = profile
|
296
|
-
# print(
|
297
|
-
# f"New locations for {authors[event.author()].get_name()}: {authors[event.author()].get_locations()}"
|
298
|
-
# )
|
299
288
|
# else:
|
300
289
|
# print(f"Unknown tag: {standardized_tag}")
|
301
290
|
|
@@ -311,6 +300,9 @@ class NostrClient:
|
|
311
300
|
|
312
301
|
Returns:
|
313
302
|
List[StallData]: list of stalls from the seller
|
303
|
+
|
304
|
+
Raises:
|
305
|
+
RuntimeError: if the stalls can't be retrieved
|
314
306
|
"""
|
315
307
|
stalls = []
|
316
308
|
try:
|
@@ -322,12 +314,12 @@ class NostrClient:
|
|
322
314
|
content = event.content()
|
323
315
|
stall = StallData.from_json(content)
|
324
316
|
stalls.append(stall)
|
325
|
-
except
|
326
|
-
self.logger.warning(
|
317
|
+
except RuntimeError as e:
|
318
|
+
self.logger.warning("Failed to parse stall data: %s", e)
|
327
319
|
continue
|
328
320
|
return stalls
|
329
321
|
except Exception as e:
|
330
|
-
raise RuntimeError(f"Failed to retrieve stalls: {e}")
|
322
|
+
raise RuntimeError(f"Failed to retrieve stalls: {e}") from e
|
331
323
|
|
332
324
|
@classmethod
|
333
325
|
def set_logging_level(cls, logging_level: int) -> None:
|
@@ -339,15 +331,18 @@ class NostrClient:
|
|
339
331
|
cls.logger.setLevel(logging_level)
|
340
332
|
for handler in cls.logger.handlers:
|
341
333
|
handler.setLevel(logging_level)
|
342
|
-
cls.logger.info(
|
334
|
+
cls.logger.info("Logging level set to %s", logging.getLevelName(logging_level))
|
343
335
|
|
344
|
-
#
|
345
|
-
#
|
346
|
-
#
|
336
|
+
# ----------------------------------------------------------------
|
337
|
+
# internal async functions.
|
338
|
+
# Developers should use synchronous functions above
|
339
|
+
# ----------------------------------------------------------------
|
347
340
|
|
348
341
|
async def _async_connect(self) -> None:
|
349
|
-
"""
|
350
|
-
|
342
|
+
"""
|
343
|
+
Asynchronous function to add relay to the NostrClient
|
344
|
+
instance and connect to it.
|
345
|
+
|
351
346
|
|
352
347
|
Raises:
|
353
348
|
RuntimeError: if the relay can't be connected to
|
@@ -356,7 +351,7 @@ class NostrClient:
|
|
356
351
|
if not self.connected:
|
357
352
|
try:
|
358
353
|
await self.client.add_relay(self.relay)
|
359
|
-
NostrClient.logger.info(
|
354
|
+
NostrClient.logger.info("Relay %s successfully added.", self.relay)
|
360
355
|
await self.client.connect()
|
361
356
|
await asyncio.sleep(2) # give time for slower connections
|
362
357
|
NostrClient.logger.info("Connected to relay.")
|
@@ -364,7 +359,7 @@ class NostrClient:
|
|
364
359
|
except Exception as e:
|
365
360
|
raise RuntimeError(
|
366
361
|
f"Unable to connect to relay {self.relay}. Exception: {e}."
|
367
|
-
)
|
362
|
+
) from e
|
368
363
|
|
369
364
|
async def _async_publish_event(self, event_builder: EventBuilder) -> EventId:
|
370
365
|
"""
|
@@ -380,9 +375,9 @@ class NostrClient:
|
|
380
375
|
await self._async_connect()
|
381
376
|
|
382
377
|
# Add debug logging
|
383
|
-
NostrClient.logger.debug(
|
378
|
+
NostrClient.logger.debug("Attempting to publish event: %s", event_builder)
|
384
379
|
NostrClient.logger.debug(
|
385
|
-
|
380
|
+
"Using keys: %s", self.keys.public_key().to_bech32()
|
386
381
|
)
|
387
382
|
|
388
383
|
# Wait for connection and try to publish
|
@@ -392,19 +387,18 @@ class NostrClient:
|
|
392
387
|
if not output:
|
393
388
|
raise RuntimeError("No output received from send_event_builder")
|
394
389
|
if len(output.success) == 0:
|
395
|
-
|
396
|
-
|
397
|
-
)
|
390
|
+
reason = getattr(output, "message", "unknown")
|
391
|
+
raise RuntimeError(f"Event rejected by relay. Reason: {reason}")
|
398
392
|
|
399
393
|
NostrClient.logger.info(
|
400
|
-
|
394
|
+
"Event published with event id: %s", output.id.to_bech32()
|
401
395
|
)
|
402
396
|
return output.id
|
403
397
|
|
404
398
|
except Exception as e:
|
405
|
-
NostrClient.logger.error(
|
399
|
+
NostrClient.logger.error("Failed to publish event: %s", str(e))
|
406
400
|
NostrClient.logger.debug("Event details:", exc_info=True)
|
407
|
-
raise RuntimeError(f"Unable to publish event: {str(e)}")
|
401
|
+
raise RuntimeError(f"Unable to publish event: {str(e)}") from e
|
408
402
|
|
409
403
|
async def _async_publish_note(self, text: str) -> EventId:
|
410
404
|
"""
|
@@ -424,7 +418,8 @@ class NostrClient:
|
|
424
418
|
|
425
419
|
async def _async_publish_product(self, product: MerchantProduct) -> EventId:
|
426
420
|
"""
|
427
|
-
Asynchronous function to create or update a NIP-15
|
421
|
+
Asynchronous function to create or update a NIP-15
|
422
|
+
Marketplace product with event kind 30018
|
428
423
|
|
429
424
|
Args:
|
430
425
|
product: product to publish
|
@@ -443,7 +438,8 @@ class NostrClient:
|
|
443
438
|
# We use the function to create the content field and discard the eventbuilder
|
444
439
|
bad_event_builder = EventBuilder.product_data(product.to_product_data())
|
445
440
|
|
446
|
-
# create an event from bad_event_builder to extract the content -
|
441
|
+
# create an event from bad_event_builder to extract the content -
|
442
|
+
# not broadcasted
|
447
443
|
bad_event = await self.client.sign_event_builder(bad_event_builder)
|
448
444
|
content = bad_event.content()
|
449
445
|
|
@@ -451,7 +447,7 @@ class NostrClient:
|
|
451
447
|
good_event_builder = EventBuilder(Kind(30018), content).tags(
|
452
448
|
[Tag.identifier(product.id), Tag.coordinate(coordinate_tag)]
|
453
449
|
)
|
454
|
-
|
450
|
+
NostrClient.logger.info("Product event: %s", good_event_builder)
|
455
451
|
return await self._async_publish_event(good_event_builder)
|
456
452
|
|
457
453
|
async def _async_publish_profile(
|
@@ -480,7 +476,8 @@ class NostrClient:
|
|
480
476
|
|
481
477
|
async def _async_publish_stall(self, stall: MerchantStall) -> EventId:
|
482
478
|
"""
|
483
|
-
Asynchronous function to create or update a NIP-15
|
479
|
+
Asynchronous function to create or update a NIP-15
|
480
|
+
Marketplace stall with event kind 30017
|
484
481
|
|
485
482
|
Args:
|
486
483
|
stall: stall to be published
|
@@ -496,7 +493,7 @@ class NostrClient:
|
|
496
493
|
# [Tag.identifier(product.id), Tag.coordinate(coordinate_tag)]
|
497
494
|
# )
|
498
495
|
|
499
|
-
|
496
|
+
NostrClient.logger.info("Merchant Stall: %s", stall)
|
500
497
|
event_builder = EventBuilder.stall_data(stall.to_stall_data()).tags(
|
501
498
|
[
|
502
499
|
Tag.custom(
|
@@ -521,16 +518,16 @@ class NostrClient:
|
|
521
518
|
try:
|
522
519
|
await self._async_connect()
|
523
520
|
except Exception as e:
|
524
|
-
raise RuntimeError("Unable to connect to the relay")
|
521
|
+
raise RuntimeError("Unable to connect to the relay") from e
|
525
522
|
|
526
523
|
try:
|
527
|
-
|
524
|
+
events_filter = Filter().kind(Kind(30017))
|
528
525
|
events = await self.client.fetch_events_from(
|
529
|
-
urls=[self.relay], filter=
|
526
|
+
urls=[self.relay], filter=events_filter, timeout=timedelta(seconds=2)
|
530
527
|
)
|
531
528
|
return events
|
532
529
|
except Exception as e:
|
533
|
-
raise RuntimeError(f"Unable to retrieve stalls: {e}")
|
530
|
+
raise RuntimeError(f"Unable to retrieve stalls: {e}") from e
|
534
531
|
|
535
532
|
async def _async_retrieve_products_from_seller(self, seller: PublicKey) -> Events:
|
536
533
|
"""
|
@@ -548,17 +545,17 @@ class NostrClient:
|
|
548
545
|
try:
|
549
546
|
await self._async_connect()
|
550
547
|
except Exception as e:
|
551
|
-
raise RuntimeError("Unable to connect to the relay")
|
548
|
+
raise RuntimeError("Unable to connect to the relay") from e
|
552
549
|
|
553
550
|
try:
|
554
551
|
# print(f"Retrieving products from seller: {seller}")
|
555
|
-
|
552
|
+
events_filter = Filter().kind(Kind(30018)).authors([seller])
|
556
553
|
events = await self.client.fetch_events_from(
|
557
|
-
urls=[self.relay], filter=
|
554
|
+
urls=[self.relay], filter=events_filter, timeout=timedelta(seconds=2)
|
558
555
|
)
|
559
556
|
return events
|
560
557
|
except Exception as e:
|
561
|
-
raise RuntimeError(f"Unable to retrieve stalls: {e}")
|
558
|
+
raise RuntimeError(f"Unable to retrieve stalls: {e}") from e
|
562
559
|
|
563
560
|
async def _async_retrieve_profile(self, author: PublicKey) -> NostrProfile:
|
564
561
|
"""
|
@@ -576,7 +573,7 @@ class NostrClient:
|
|
576
573
|
try:
|
577
574
|
await self._async_connect()
|
578
575
|
except Exception as e:
|
579
|
-
raise RuntimeError("Unable to connect to the relay")
|
576
|
+
raise RuntimeError("Unable to connect to the relay") from e
|
580
577
|
|
581
578
|
try:
|
582
579
|
metadata = await self.client.fetch_metadata(
|
@@ -584,7 +581,7 @@ class NostrClient:
|
|
584
581
|
)
|
585
582
|
return NostrProfile.from_metadata(metadata, author)
|
586
583
|
except Exception as e:
|
587
|
-
raise RuntimeError(f"Unable to retrieve metadata: {e}")
|
584
|
+
raise RuntimeError(f"Unable to retrieve metadata: {e}") from e
|
588
585
|
|
589
586
|
async def _async_retrieve_stalls_from_seller(self, seller: PublicKey) -> Events:
|
590
587
|
"""
|
@@ -602,16 +599,16 @@ class NostrClient:
|
|
602
599
|
try:
|
603
600
|
await self._async_connect()
|
604
601
|
except Exception as e:
|
605
|
-
raise RuntimeError("Unable to connect to the relay")
|
602
|
+
raise RuntimeError("Unable to connect to the relay") from e
|
606
603
|
|
607
604
|
try:
|
608
|
-
|
605
|
+
events_filter = Filter().kind(Kind(30017)).authors([seller])
|
609
606
|
events = await self.client.fetch_events_from(
|
610
|
-
urls=[self.relay], filter=
|
607
|
+
urls=[self.relay], filter=events_filter, timeout=timedelta(seconds=2)
|
611
608
|
)
|
612
609
|
return events
|
613
610
|
except Exception as e:
|
614
|
-
raise RuntimeError(f"Unable to retrieve stalls: {e}")
|
611
|
+
raise RuntimeError(f"Unable to retrieve stalls: {e}") from e
|
615
612
|
|
616
613
|
|
617
614
|
def generate_and_save_keys(env_var: str, env_path: Path) -> Keys:
|
@@ -635,7 +632,7 @@ def generate_and_save_keys(env_var: str, env_path: Path) -> Keys:
|
|
635
632
|
# Read existing .env content
|
636
633
|
env_content = ""
|
637
634
|
if env_path.exists():
|
638
|
-
with open(env_path, "r") as f:
|
635
|
+
with open(env_path, "r", encoding="utf-8") as f:
|
639
636
|
env_content = f.read()
|
640
637
|
|
641
638
|
# Check if the env var already exists
|
@@ -655,7 +652,7 @@ def generate_and_save_keys(env_var: str, env_path: Path) -> Keys:
|
|
655
652
|
new_lines.append(f"{env_var}={nsec}")
|
656
653
|
|
657
654
|
# Write back to .env
|
658
|
-
with open(env_path, "w") as f:
|
655
|
+
with open(env_path, "w", encoding="utf-8") as f:
|
659
656
|
f.write("\n".join(new_lines))
|
660
657
|
if new_lines: # Add final newline if there's content
|
661
658
|
f.write("\n")
|
agentstr/nostr.pyi
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
"""
|
2
|
+
Type stubs for the Nostr module.
|
3
|
+
|
4
|
+
This file provides type annotations for the Nostr module, enabling better
|
5
|
+
type checking and autocompletion in IDEs. It defines the expected types
|
6
|
+
for classes, functions, and variables used within the Nostr module.
|
7
|
+
|
8
|
+
Note: This is a type stub file and does not contain any executable code.
|
9
|
+
"""
|
10
|
+
|
1
11
|
from logging import Logger
|
2
12
|
from pathlib import Path
|
3
13
|
from typing import ClassVar, List, Optional
|
@@ -17,7 +27,6 @@ from nostr_sdk import ( # type: ignore
|
|
17
27
|
ShippingCost,
|
18
28
|
ShippingMethod,
|
19
29
|
StallData,
|
20
|
-
Tag,
|
21
30
|
Timestamp,
|
22
31
|
)
|
23
32
|
|
@@ -41,6 +50,11 @@ __all__ = [
|
|
41
50
|
]
|
42
51
|
|
43
52
|
class NostrClient:
|
53
|
+
"""
|
54
|
+
NostrClient implements the set of Nostr utilities required for higher level functions
|
55
|
+
implementations like the Marketplace.
|
56
|
+
"""
|
57
|
+
|
44
58
|
logger: ClassVar[Logger]
|
45
59
|
relay: str
|
46
60
|
keys: Keys
|
@@ -1,8 +1,8 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: agentstr
|
3
|
-
Version: 0.1.
|
4
|
-
Summary:
|
5
|
-
Author-email: Synvya <
|
3
|
+
Version: 0.1.13
|
4
|
+
Summary: Tools for a Nostr agentic ecosystem
|
5
|
+
Author-email: Synvya <synvya@synvya.com>
|
6
6
|
License: MIT
|
7
7
|
Project-URL: Homepage, https://www.synvya.com
|
8
8
|
Project-URL: Repository, https://github.com/synvya/agentstr
|
@@ -13,18 +13,21 @@ Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
14
14
|
Requires-Dist: agno>=1.1.1
|
15
15
|
Requires-Dist: openai>=1.50.0
|
16
|
-
Requires-Dist: packaging>=24.0
|
17
16
|
Requires-Dist: nostr_sdk>=0.39.0
|
18
17
|
Requires-Dist: pydantic>=2.0.0
|
19
|
-
Requires-Dist: cassandra-driver>=3.29.2
|
20
|
-
Requires-Dist: cassio>=0.1.10
|
21
18
|
Provides-Extra: dev
|
22
19
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
23
20
|
Requires-Dist: pytest-asyncio>=0.23.5; extra == "dev"
|
24
21
|
Requires-Dist: black>=23.0; extra == "dev"
|
25
22
|
Requires-Dist: isort>=5.0; extra == "dev"
|
26
23
|
Requires-Dist: mypy>=1.0; extra == "dev"
|
27
|
-
Requires-Dist:
|
24
|
+
Requires-Dist: pylint>=3.0; extra == "dev"
|
25
|
+
Provides-Extra: examples
|
26
|
+
Requires-Dist: python-dotenv>=1.0; extra == "examples"
|
27
|
+
Requires-Dist: cassandra-driver>=3.29.2; extra == "examples"
|
28
|
+
Requires-Dist: cassio>=0.1.10; extra == "examples"
|
29
|
+
Requires-Dist: fastapi>=0.110.0; extra == "examples"
|
30
|
+
Requires-Dist: uvicorn>=0.30.0; extra == "examples"
|
28
31
|
|
29
32
|
# AgentStr
|
30
33
|
|
@@ -0,0 +1,15 @@
|
|
1
|
+
agentstr/__init__.py,sha256=rbFtFOldBSbeDX_8Xva3YHRC9c3nPFA9mJXS3UZhJ4s,1294
|
2
|
+
agentstr/buyer.py,sha256=I1tApMjlq0Bg5Qk7twcAbF2rf_L5I2L9qzQO5sKv2RE,9912
|
3
|
+
agentstr/buyer.pyi,sha256=AqdKa7nQWohxHaoKHjCNpOpDB6rsWGWAlASNhwEw45g,1219
|
4
|
+
agentstr/merchant.py,sha256=3Fz4hrxyb5m7Kk47RodNC-Vyjm9iV7bI4ncPF8EMkhw,35078
|
5
|
+
agentstr/merchant.pyi,sha256=mqak--H7D_b7o8JNQlQRmov2An-defyBGRJNhMNchXQ,1437
|
6
|
+
agentstr/models.py,sha256=lEoopEQYIqI0egVxoXhBLlBh35C0WQmR_oBKm5Sexwk,11423
|
7
|
+
agentstr/models.pyi,sha256=k1_D-afE17gybHhtQMqA6cx7H5lYh7Fg0gbXclxyP4A,2857
|
8
|
+
agentstr/nostr.py,sha256=u2jDwQDmg01UCThDy1WW9lOPvAHyI1WMbtxFFi_2q08,22703
|
9
|
+
agentstr/nostr.pyi,sha256=nlBdOOI6vPboACjBvrQZKHy3BtCjboaClG9ZVD2X8XQ,3118
|
10
|
+
agentstr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
|
+
agentstr-0.1.13.dist-info/LICENSE,sha256=20H0yoEDN5XO1xPXyZCyJjvSTP0YiarRMKWPfiaBhQY,1063
|
12
|
+
agentstr-0.1.13.dist-info/METADATA,sha256=JBXZQ5tw7lPrir_3TKkbYYXpLMqHlMofkfdwXKTP6Gk,4667
|
13
|
+
agentstr-0.1.13.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
14
|
+
agentstr-0.1.13.dist-info/top_level.txt,sha256=KZObFRHppZvKUGYB_m9w5HhLwps7jj7w6Xrw73dH2ss,9
|
15
|
+
agentstr-0.1.13.dist-info/RECORD,,
|
agentstr-0.1.12.dist-info/RECORD
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
agentstr/__init__.py,sha256=t2nKJcQ0cq3uYc5sadpJGXSO08UViAEfifCQE9pa068,800
|
2
|
-
agentstr/buyer.py,sha256=rifdCC4FSmbLimA94AhSp3UaCA5vxtH61j_KgVJwkVo,9848
|
3
|
-
agentstr/buyer.pyi,sha256=Zs2SbItabztcGg_cJF8_5Mf-p7Me34hxoFHEkC1inDU,1168
|
4
|
-
agentstr/merchant.py,sha256=Uf3LkV5HlyuOZBA-covkUnDYaxI91yHvmVYOD8kk4cM,35225
|
5
|
-
agentstr/merchant.pyi,sha256=bAYP8qz9GTGNeyFEkZsc4CqdCsC2HV4nrV0dWyYu1VY,1440
|
6
|
-
agentstr/models.py,sha256=-anXkTmOANTKw-elxKG-RZCarCGBIWlUHiUoMBrE3FQ,11488
|
7
|
-
agentstr/models.pyi,sha256=QVnjv01cuMynOjzuEcct72yO-LUWpdxEeiEzcf7QTl4,2867
|
8
|
-
agentstr/nostr.py,sha256=VSC5gDN8XM54BSlfQ0yYv2N3DdTVVviWg5VJkJOD5R0,23199
|
9
|
-
agentstr/nostr.pyi,sha256=A-Dq0ZVuZuXEXnMfgVhKRym4ubFnsSjFrgH1Db1JMCI,2647
|
10
|
-
agentstr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
|
-
agentstr-0.1.12.dist-info/LICENSE,sha256=20H0yoEDN5XO1xPXyZCyJjvSTP0YiarRMKWPfiaBhQY,1063
|
12
|
-
agentstr-0.1.12.dist-info/METADATA,sha256=idnYX1pNr1qsbQXDOTdHmmPBEy3pa9RLdinZn6PeIsE,4475
|
13
|
-
agentstr-0.1.12.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
14
|
-
agentstr-0.1.12.dist-info/top_level.txt,sha256=KZObFRHppZvKUGYB_m9w5HhLwps7jj7w6Xrw73dH2ss,9
|
15
|
-
agentstr-0.1.12.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|