agentstr 0.1.10__py3-none-any.whl → 0.1.12__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 +20 -13
- agentstr/buyer.py +291 -0
- agentstr/buyer.pyi +31 -0
- agentstr/{marketplace.py → merchant.py} +126 -323
- agentstr/merchant.pyi +37 -0
- agentstr/models.py +381 -0
- agentstr/models.pyi +103 -0
- agentstr/nostr.py +389 -53
- agentstr/nostr.pyi +82 -0
- agentstr/py.typed +0 -0
- agentstr-0.1.12.dist-info/METADATA +129 -0
- agentstr-0.1.12.dist-info/RECORD +15 -0
- agentstr/examples/basic_cli/.env.example +0 -2
- agentstr/examples/basic_cli/README.md +0 -11
- agentstr/examples/basic_cli/main.py +0 -193
- agentstr-0.1.10.dist-info/METADATA +0 -133
- agentstr-0.1.10.dist-info/RECORD +0 -11
- {agentstr-0.1.10.dist-info → agentstr-0.1.12.dist-info}/LICENSE +0 -0
- {agentstr-0.1.10.dist-info → agentstr-0.1.12.dist-info}/WHEEL +0 -0
- {agentstr-0.1.10.dist-info → agentstr-0.1.12.dist-info}/top_level.txt +0 -0
agentstr/merchant.pyi
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
from typing import Any, List
|
2
|
+
|
3
|
+
from agno.tools import Toolkit
|
4
|
+
from nostr_sdk import NostrClient # type: ignore
|
5
|
+
|
6
|
+
from agentstr.models import AgentProfile, MerchantProduct, MerchantStall
|
7
|
+
|
8
|
+
class MerchantTools(Toolkit):
|
9
|
+
_nostr_client: NostrClient
|
10
|
+
_profile: AgentProfile
|
11
|
+
_stalls: List[MerchantStall]
|
12
|
+
_products: List[MerchantProduct]
|
13
|
+
|
14
|
+
def __init__(
|
15
|
+
self,
|
16
|
+
merchant_profile: AgentProfile,
|
17
|
+
relay: str,
|
18
|
+
stalls: List[MerchantStall],
|
19
|
+
products: List[MerchantProduct],
|
20
|
+
) -> None: ...
|
21
|
+
def get_profile(self) -> str: ...
|
22
|
+
def get_relay(self) -> str: ...
|
23
|
+
def get_products(self) -> str: ...
|
24
|
+
def get_stalls(self) -> str: ...
|
25
|
+
def publish_all_products(self) -> str: ...
|
26
|
+
def publish_all_stalls(self) -> str: ...
|
27
|
+
def publish_new_product(self, product: MerchantProduct) -> str: ...
|
28
|
+
def publish_product_by_name(self, product_name: str) -> str: ...
|
29
|
+
def publish_products_by_stall_name(self, stall_name: str) -> str: ...
|
30
|
+
def publish_profile(self) -> str: ...
|
31
|
+
def publish_new_stall(self, stall: MerchantStall) -> str: ...
|
32
|
+
def publish_stall_by_name(self, stall_name: str) -> str: ...
|
33
|
+
def remove_all_products(self) -> str: ...
|
34
|
+
def remove_all_stalls(self) -> str: ...
|
35
|
+
def remove_product_by_name(self, product_name: str) -> str: ...
|
36
|
+
def remove_stall_by_name(self, stall_name: str) -> str: ...
|
37
|
+
def get_event_id(self, response: Any) -> str: ...
|
agentstr/models.py
ADDED
@@ -0,0 +1,381 @@
|
|
1
|
+
import json
|
2
|
+
import logging
|
3
|
+
from typing import List, Optional
|
4
|
+
|
5
|
+
from nostr_sdk import (
|
6
|
+
Keys,
|
7
|
+
Metadata,
|
8
|
+
ProductData,
|
9
|
+
PublicKey,
|
10
|
+
ShippingCost,
|
11
|
+
ShippingMethod,
|
12
|
+
StallData,
|
13
|
+
)
|
14
|
+
from pydantic import BaseModel, ConfigDict, Field
|
15
|
+
|
16
|
+
__all__ = [
|
17
|
+
"Profile",
|
18
|
+
"NostrProfile",
|
19
|
+
"AgentProfile",
|
20
|
+
"MerchantProduct",
|
21
|
+
"MerchantStall",
|
22
|
+
]
|
23
|
+
|
24
|
+
|
25
|
+
class Profile:
|
26
|
+
"""
|
27
|
+
Generic Profile class that holds the metadata of a Nostr profile.
|
28
|
+
"""
|
29
|
+
|
30
|
+
logger = logging.getLogger("Profile")
|
31
|
+
|
32
|
+
def __init__(self) -> None:
|
33
|
+
self.about = ""
|
34
|
+
self.display_name = ""
|
35
|
+
self.name = ""
|
36
|
+
self.picture = ""
|
37
|
+
self.website = ""
|
38
|
+
|
39
|
+
def get_about(self) -> str:
|
40
|
+
return self.about
|
41
|
+
|
42
|
+
def get_display_name(self) -> str:
|
43
|
+
return self.display_name
|
44
|
+
|
45
|
+
def get_name(self) -> str:
|
46
|
+
return self.name
|
47
|
+
|
48
|
+
def get_picture(self) -> str:
|
49
|
+
return self.picture
|
50
|
+
|
51
|
+
def get_website(self) -> str:
|
52
|
+
return self.website
|
53
|
+
|
54
|
+
def set_about(self, about: str) -> None:
|
55
|
+
self.about = about
|
56
|
+
|
57
|
+
def set_display_name(self, display_name: str) -> None:
|
58
|
+
self.display_name = display_name
|
59
|
+
|
60
|
+
def set_name(self, name: str) -> None:
|
61
|
+
self.name = name
|
62
|
+
|
63
|
+
def set_picture(self, picture: str) -> None:
|
64
|
+
self.picture = picture
|
65
|
+
|
66
|
+
def set_website(self, website: str) -> None:
|
67
|
+
self.website = website
|
68
|
+
|
69
|
+
def to_json(self) -> str:
|
70
|
+
data = {
|
71
|
+
"name": self.name,
|
72
|
+
"display_name": self.display_name,
|
73
|
+
"about": self.about,
|
74
|
+
"picture": self.picture,
|
75
|
+
"website": self.website,
|
76
|
+
}
|
77
|
+
return json.dumps(data)
|
78
|
+
|
79
|
+
|
80
|
+
class NostrProfile(Profile):
|
81
|
+
"""
|
82
|
+
NostrProfile is a Profile that is used to represent a public Nostr profile.
|
83
|
+
|
84
|
+
Key difference between NostrProfile and AgentProfile is that NostrProfile represents
|
85
|
+
a third party profile and therefore it only has a public key.
|
86
|
+
"""
|
87
|
+
|
88
|
+
WEB_URL: str = "https://primal.net/p/"
|
89
|
+
|
90
|
+
def __init__(self, public_key: PublicKey) -> None:
|
91
|
+
super().__init__()
|
92
|
+
self.public_key = public_key
|
93
|
+
self.profile_url = self.WEB_URL + self.public_key.to_bech32()
|
94
|
+
# Initialize the locations set here, per-instance
|
95
|
+
self.locations: set[str] = set()
|
96
|
+
|
97
|
+
@classmethod
|
98
|
+
def from_metadata(cls, metadata: Metadata, public_key: PublicKey) -> "NostrProfile":
|
99
|
+
profile = cls(public_key)
|
100
|
+
profile.set_about(metadata.get_about())
|
101
|
+
profile.set_display_name(metadata.get_display_name())
|
102
|
+
profile.set_name(metadata.get_name())
|
103
|
+
profile.set_picture(metadata.get_picture())
|
104
|
+
profile.set_website(metadata.get_website())
|
105
|
+
return profile
|
106
|
+
|
107
|
+
def add_location(self, location: str) -> None:
|
108
|
+
"""Add a location to the Nostr profile.
|
109
|
+
|
110
|
+
Args:
|
111
|
+
location: location to add
|
112
|
+
"""
|
113
|
+
self.locations.add(location)
|
114
|
+
|
115
|
+
def get_public_key(self) -> str:
|
116
|
+
"""Get the public key of the Nostr profile.
|
117
|
+
|
118
|
+
Returns:
|
119
|
+
str: bech32 encoded public key of the Nostr profile
|
120
|
+
"""
|
121
|
+
return str(self.public_key.to_bech32())
|
122
|
+
|
123
|
+
def get_locations(self) -> set[str]:
|
124
|
+
"""Get the locations of the Nostr profile.
|
125
|
+
|
126
|
+
Returns:
|
127
|
+
set[str]: set with locations of the Nostr profile
|
128
|
+
"""
|
129
|
+
return self.locations
|
130
|
+
|
131
|
+
def get_profile_url(self) -> str:
|
132
|
+
return self.profile_url
|
133
|
+
|
134
|
+
def get_zip_codes(self) -> List[str]:
|
135
|
+
return self.zip_codes
|
136
|
+
|
137
|
+
def to_json(self) -> str:
|
138
|
+
# Ensure super().to_json() returns a dictionary
|
139
|
+
parent_json = super().to_json()
|
140
|
+
data = json.loads(parent_json) if isinstance(parent_json, str) else parent_json
|
141
|
+
|
142
|
+
# Add NostrProfile-specific fields
|
143
|
+
data.update(
|
144
|
+
{
|
145
|
+
"profile_url": self.profile_url,
|
146
|
+
"public_key": self.public_key.to_bech32(),
|
147
|
+
"locations": (
|
148
|
+
list(self.locations) if self.locations else []
|
149
|
+
), # Convert set to list
|
150
|
+
}
|
151
|
+
)
|
152
|
+
|
153
|
+
return json.dumps(data)
|
154
|
+
|
155
|
+
def __eq__(self, other: object) -> bool:
|
156
|
+
if not isinstance(other, NostrProfile):
|
157
|
+
return False
|
158
|
+
return str(self.public_key.to_bech32()) == str(other.public_key.to_bech32())
|
159
|
+
|
160
|
+
def __hash__(self) -> int:
|
161
|
+
return hash(str(self.public_key.to_bech32()))
|
162
|
+
|
163
|
+
def to_dict(self) -> dict:
|
164
|
+
"""
|
165
|
+
Returns a dictionary representation of the NostrProfile.
|
166
|
+
|
167
|
+
Returns:
|
168
|
+
dict: dictionary representation of the NostrProfile
|
169
|
+
"""
|
170
|
+
return {
|
171
|
+
"profile_url": self.profile_url,
|
172
|
+
"public_key": self.public_key.to_bech32(),
|
173
|
+
"locations": list(self.locations), # Convert set to list
|
174
|
+
"name": self.name,
|
175
|
+
"display_name": self.display_name,
|
176
|
+
"about": self.about,
|
177
|
+
"picture": self.picture,
|
178
|
+
"website": self.website,
|
179
|
+
}
|
180
|
+
|
181
|
+
|
182
|
+
class AgentProfile(Profile):
|
183
|
+
"""
|
184
|
+
AgentProfile is a Profile that is used to represent an agent.
|
185
|
+
"""
|
186
|
+
|
187
|
+
WEB_URL: str = "https://primal.net/p/"
|
188
|
+
|
189
|
+
def __init__(self, keys: Keys) -> None:
|
190
|
+
super().__init__()
|
191
|
+
self.keys = keys
|
192
|
+
self.profile_url = self.WEB_URL + self.keys.public_key().to_bech32()
|
193
|
+
|
194
|
+
@classmethod
|
195
|
+
def from_metadata(cls, metadata: Metadata, keys: Keys) -> "AgentProfile":
|
196
|
+
profile = cls(keys)
|
197
|
+
profile.set_about(metadata.get_about())
|
198
|
+
profile.set_display_name(metadata.get_display_name())
|
199
|
+
profile.set_name(metadata.get_name())
|
200
|
+
profile.set_picture(metadata.get_picture())
|
201
|
+
profile.set_website(metadata.get_website())
|
202
|
+
return profile
|
203
|
+
|
204
|
+
def get_private_key(self) -> str:
|
205
|
+
return str(self.keys.secret_key().to_bech32())
|
206
|
+
|
207
|
+
def get_public_key(self) -> str:
|
208
|
+
return str(self.keys.public_key().to_bech32())
|
209
|
+
|
210
|
+
def to_json(self) -> str:
|
211
|
+
# Parse parent's JSON string back to dict
|
212
|
+
data = json.loads(super().to_json())
|
213
|
+
# Add AgentProfile-specific fields
|
214
|
+
data.update(
|
215
|
+
{
|
216
|
+
"profile_url": self.profile_url,
|
217
|
+
"public_key": self.keys.public_key().to_bech32(),
|
218
|
+
"private_key": self.keys.secret_key().to_bech32(),
|
219
|
+
}
|
220
|
+
)
|
221
|
+
return json.dumps(data)
|
222
|
+
|
223
|
+
|
224
|
+
class MerchantProduct(BaseModel):
|
225
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
226
|
+
|
227
|
+
id: str
|
228
|
+
stall_id: str
|
229
|
+
name: str
|
230
|
+
description: str
|
231
|
+
images: List[str]
|
232
|
+
currency: str
|
233
|
+
price: float
|
234
|
+
quantity: int
|
235
|
+
shipping: List[ShippingCost]
|
236
|
+
categories: List[str] = Field(default_factory=list)
|
237
|
+
specs: List[List[str]] = Field(default_factory=list)
|
238
|
+
|
239
|
+
@classmethod
|
240
|
+
def from_product_data(cls, product_data: "ProductData") -> "MerchantProduct":
|
241
|
+
# print(f"Raw product data specs: {product_data.specs}") # Debug print
|
242
|
+
shipping_costs = []
|
243
|
+
for ship in product_data.shipping:
|
244
|
+
if isinstance(ship, dict):
|
245
|
+
shipping_costs.append(ShippingCost(id=ship["id"], cost=ship["cost"]))
|
246
|
+
else:
|
247
|
+
shipping_costs.append(ship)
|
248
|
+
|
249
|
+
# Handle specs - ensure it's a list
|
250
|
+
specs = []
|
251
|
+
if product_data.specs is not None:
|
252
|
+
if isinstance(product_data.specs, dict):
|
253
|
+
# Convert dict to list of lists if needed
|
254
|
+
specs = [[k, v] for k, v in product_data.specs.items()]
|
255
|
+
elif isinstance(product_data.specs, list):
|
256
|
+
specs = product_data.specs
|
257
|
+
|
258
|
+
return cls(
|
259
|
+
id=product_data.id,
|
260
|
+
stall_id=product_data.stall_id,
|
261
|
+
name=product_data.name,
|
262
|
+
description=product_data.description,
|
263
|
+
images=product_data.images,
|
264
|
+
currency=product_data.currency,
|
265
|
+
price=product_data.price,
|
266
|
+
quantity=product_data.quantity,
|
267
|
+
shipping=shipping_costs,
|
268
|
+
categories=(
|
269
|
+
product_data.categories if product_data.categories is not None else []
|
270
|
+
),
|
271
|
+
specs=specs,
|
272
|
+
)
|
273
|
+
|
274
|
+
def to_product_data(self) -> "ProductData":
|
275
|
+
try:
|
276
|
+
return ProductData(
|
277
|
+
id=self.id,
|
278
|
+
stall_id=self.stall_id,
|
279
|
+
name=self.name,
|
280
|
+
description=self.description,
|
281
|
+
images=self.images,
|
282
|
+
currency=self.currency,
|
283
|
+
price=self.price,
|
284
|
+
quantity=self.quantity,
|
285
|
+
shipping=self.shipping,
|
286
|
+
categories=self.categories,
|
287
|
+
specs=self.specs,
|
288
|
+
)
|
289
|
+
except Exception as e:
|
290
|
+
logging.error(f"Failed to convert to ProductData: {e}")
|
291
|
+
logging.error(f"Shipping data: {self.shipping}")
|
292
|
+
raise
|
293
|
+
|
294
|
+
def to_dict(self) -> dict:
|
295
|
+
"""
|
296
|
+
Returns a dictionary representation of the MerchantProduct.
|
297
|
+
ShippingCost class is not serializable, so we need to convert it to a dictionary.
|
298
|
+
|
299
|
+
Returns:
|
300
|
+
dict: dictionary representation of the MerchantProduct
|
301
|
+
"""
|
302
|
+
shipping_dicts = []
|
303
|
+
for shipping in self.shipping:
|
304
|
+
shipping_dicts.append({"id": shipping.id, "cost": shipping.cost})
|
305
|
+
|
306
|
+
return {
|
307
|
+
"id": self.id,
|
308
|
+
"stall_id": self.stall_id,
|
309
|
+
"name": self.name,
|
310
|
+
"description": self.description,
|
311
|
+
"images": self.images,
|
312
|
+
"currency": self.currency,
|
313
|
+
"price": self.price,
|
314
|
+
"quantity": self.quantity,
|
315
|
+
"shipping": shipping_dicts,
|
316
|
+
"categories": self.categories,
|
317
|
+
"specs": self.specs,
|
318
|
+
}
|
319
|
+
|
320
|
+
|
321
|
+
class MerchantStall(BaseModel):
|
322
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
323
|
+
|
324
|
+
id: str
|
325
|
+
name: str
|
326
|
+
description: str
|
327
|
+
currency: str
|
328
|
+
shipping: List[ShippingMethod]
|
329
|
+
geohash: str
|
330
|
+
|
331
|
+
@classmethod
|
332
|
+
def from_stall_data(cls, stall: "StallData") -> "MerchantStall":
|
333
|
+
return cls(
|
334
|
+
id=stall.id(),
|
335
|
+
name=stall.name(),
|
336
|
+
description=stall.description(),
|
337
|
+
currency=stall.currency(),
|
338
|
+
shipping=stall.shipping(),
|
339
|
+
)
|
340
|
+
|
341
|
+
def get_geohash(self) -> str:
|
342
|
+
return self.geohash
|
343
|
+
|
344
|
+
def set_geohash(self, geohash: str) -> None:
|
345
|
+
self.geohash = geohash
|
346
|
+
|
347
|
+
def to_dict(self) -> dict:
|
348
|
+
"""
|
349
|
+
Returns a dictionary representation of the MerchantStall.
|
350
|
+
ShippingMethod class is not serializable, so we need to convert it to a dictionary.
|
351
|
+
We can only access cost and id from the ShippingMethod class. We can't access name or regions.
|
352
|
+
|
353
|
+
Returns:
|
354
|
+
dict: dictionary representation of the MerchantStall
|
355
|
+
"""
|
356
|
+
shipping_dicts = []
|
357
|
+
for shipping in self.shipping:
|
358
|
+
shipping_dicts.append(
|
359
|
+
{
|
360
|
+
"cost": shipping.get_shipping_cost().cost,
|
361
|
+
"id": shipping.get_shipping_cost().id,
|
362
|
+
}
|
363
|
+
)
|
364
|
+
|
365
|
+
return {
|
366
|
+
"id": self.id,
|
367
|
+
"name": self.name,
|
368
|
+
"description": self.description,
|
369
|
+
"currency": self.currency,
|
370
|
+
"shipping zones": [shipping_dicts],
|
371
|
+
"geohash": self.geohash,
|
372
|
+
}
|
373
|
+
|
374
|
+
def to_stall_data(self) -> "StallData":
|
375
|
+
return StallData(
|
376
|
+
self.id,
|
377
|
+
self.name,
|
378
|
+
self.description,
|
379
|
+
self.currency,
|
380
|
+
self.shipping, # No conversion needed
|
381
|
+
)
|
agentstr/models.pyi
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
from logging import Logger
|
2
|
+
from typing import ClassVar, List, Optional, Set
|
3
|
+
|
4
|
+
from nostr_sdk import (
|
5
|
+
Keys,
|
6
|
+
Metadata,
|
7
|
+
ProductData,
|
8
|
+
PublicKey,
|
9
|
+
ShippingCost,
|
10
|
+
ShippingMethod,
|
11
|
+
StallData,
|
12
|
+
)
|
13
|
+
from pydantic import BaseModel
|
14
|
+
|
15
|
+
class Profile:
|
16
|
+
"""
|
17
|
+
Generic Profile class that holds the metadata of a Nostr profile.
|
18
|
+
"""
|
19
|
+
|
20
|
+
logger: ClassVar[Logger]
|
21
|
+
about: str
|
22
|
+
display_name: str
|
23
|
+
name: str
|
24
|
+
picture: str
|
25
|
+
website: str
|
26
|
+
|
27
|
+
def __init__(self) -> None: ...
|
28
|
+
def get_about(self) -> str: ...
|
29
|
+
def get_display_name(self) -> str: ...
|
30
|
+
def get_name(self) -> str: ...
|
31
|
+
def get_picture(self) -> str: ...
|
32
|
+
def get_website(self) -> str: ...
|
33
|
+
def set_about(self, about: str) -> None: ...
|
34
|
+
def set_display_name(self, display_name: str) -> None: ...
|
35
|
+
def set_name(self, name: str) -> None: ...
|
36
|
+
def set_picture(self, picture: str) -> None: ...
|
37
|
+
def set_website(self, website: str) -> None: ...
|
38
|
+
def to_json(self) -> str: ...
|
39
|
+
|
40
|
+
class AgentProfile(Profile):
|
41
|
+
WEB_URL: ClassVar[str]
|
42
|
+
profile_url: str
|
43
|
+
keys: Keys
|
44
|
+
|
45
|
+
def __init__(self, keys: Keys) -> None: ...
|
46
|
+
@classmethod
|
47
|
+
def from_metadata(cls, metadata: Metadata, keys: Keys) -> "AgentProfile": ...
|
48
|
+
def get_private_key(self) -> str: ...
|
49
|
+
def get_public_key(self) -> str: ...
|
50
|
+
def to_json(self) -> str: ...
|
51
|
+
|
52
|
+
class NostrProfile(Profile):
|
53
|
+
public_key: PublicKey
|
54
|
+
profile_url: str
|
55
|
+
WEB_URL: ClassVar[str]
|
56
|
+
locations: Set[str]
|
57
|
+
|
58
|
+
def __init__(self, public_key: PublicKey) -> None: ...
|
59
|
+
@classmethod
|
60
|
+
def from_metadata(
|
61
|
+
cls, metadata: Metadata, public_key: PublicKey
|
62
|
+
) -> "NostrProfile": ...
|
63
|
+
def add_location(self, location: str) -> None: ...
|
64
|
+
def get_public_key(self) -> str: ...
|
65
|
+
def get_locations(self) -> Set[str]: ...
|
66
|
+
def get_profile_url(self) -> str: ...
|
67
|
+
def get_zip_codes(self) -> List[str]: ...
|
68
|
+
def to_json(self) -> str: ...
|
69
|
+
def __eq__(self, other: object) -> bool: ...
|
70
|
+
def __hash__(self) -> int: ...
|
71
|
+
|
72
|
+
class MerchantProduct(BaseModel):
|
73
|
+
id: str
|
74
|
+
stall_id: str
|
75
|
+
name: str
|
76
|
+
description: str
|
77
|
+
images: List[str]
|
78
|
+
currency: str
|
79
|
+
price: float
|
80
|
+
quantity: int
|
81
|
+
shipping: List[ShippingCost]
|
82
|
+
categories: List[str]
|
83
|
+
specs: List[List[str]]
|
84
|
+
|
85
|
+
@classmethod
|
86
|
+
def from_product_data(cls, product_data: ProductData) -> "MerchantProduct": ...
|
87
|
+
def to_product_data(self) -> ProductData: ...
|
88
|
+
def to_dict(self) -> dict: ...
|
89
|
+
|
90
|
+
class MerchantStall(BaseModel):
|
91
|
+
id: str
|
92
|
+
name: str
|
93
|
+
description: str
|
94
|
+
currency: str
|
95
|
+
shipping: List[ShippingMethod]
|
96
|
+
geohash: str
|
97
|
+
|
98
|
+
@classmethod
|
99
|
+
def from_stall_data(cls, stall_data: StallData) -> "MerchantStall": ...
|
100
|
+
def get_geohash(self) -> str: ...
|
101
|
+
def set_geohash(self, geohash: str) -> None: ...
|
102
|
+
def to_dict(self) -> dict: ...
|
103
|
+
def to_stall_data(self) -> StallData: ...
|