agentstr 0.1.11__py3-none-any.whl → 0.1.13__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
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_nostr_client(self) -> NostrClient: ...
22
+ def get_profile(self) -> str: ...
23
+ def get_relay(self) -> str: ...
24
+ def get_products(self) -> str: ...
25
+ def get_stalls(self) -> str: ...
26
+ def publish_all_products(self) -> str: ...
27
+ def publish_all_stalls(self) -> str: ...
28
+ def publish_new_product(self, product: MerchantProduct) -> str: ...
29
+ def publish_product_by_name(self, product_name: str) -> str: ...
30
+ def publish_products_by_stall_name(self, stall_name: str) -> str: ...
31
+ def publish_profile(self) -> str: ...
32
+ def publish_new_stall(self, stall: MerchantStall) -> str: ...
33
+ def publish_stall_by_name(self, stall_name: str) -> str: ...
34
+ def remove_all_products(self) -> str: ...
35
+ def remove_all_stalls(self) -> str: ...
36
+ def remove_product_by_name(self, product_name: str) -> str: ...
37
+ def remove_stall_by_name(self, stall_name: str) -> str: ...
agentstr/models.py ADDED
@@ -0,0 +1,380 @@
1
+ import json
2
+ import logging
3
+ from typing import List
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 to_json(self) -> str:
135
+ # Ensure super().to_json() returns a dictionary
136
+ parent_json = super().to_json()
137
+ data = json.loads(parent_json) if isinstance(parent_json, str) else parent_json
138
+
139
+ # Add NostrProfile-specific fields
140
+ data.update(
141
+ {
142
+ "profile_url": self.profile_url,
143
+ "public_key": self.public_key.to_bech32(),
144
+ "locations": (
145
+ list(self.locations) if self.locations else []
146
+ ), # Convert set to list
147
+ }
148
+ )
149
+
150
+ return json.dumps(data)
151
+
152
+ def __eq__(self, other: object) -> bool:
153
+ if not isinstance(other, NostrProfile):
154
+ return False
155
+ return str(self.public_key.to_bech32()) == str(other.public_key.to_bech32())
156
+
157
+ def __hash__(self) -> int:
158
+ return hash(str(self.public_key.to_bech32()))
159
+
160
+ def to_dict(self) -> dict:
161
+ """
162
+ Returns a dictionary representation of the NostrProfile.
163
+
164
+ Returns:
165
+ dict: dictionary representation of the NostrProfile
166
+ """
167
+ return {
168
+ "profile_url": self.profile_url,
169
+ "public_key": self.public_key.to_bech32(),
170
+ "locations": list(self.locations), # Convert set to list
171
+ "name": self.name,
172
+ "display_name": self.display_name,
173
+ "about": self.about,
174
+ "picture": self.picture,
175
+ "website": self.website,
176
+ }
177
+
178
+
179
+ class AgentProfile(Profile):
180
+ """
181
+ AgentProfile is a Profile that is used to represent an agent.
182
+ """
183
+
184
+ WEB_URL: str = "https://primal.net/p/"
185
+
186
+ def __init__(self, keys: Keys) -> None:
187
+ super().__init__()
188
+ self.keys = keys
189
+ self.profile_url = self.WEB_URL + self.keys.public_key().to_bech32()
190
+
191
+ @classmethod
192
+ def from_metadata(cls, metadata: Metadata, keys: Keys) -> "AgentProfile":
193
+ profile = cls(keys)
194
+ profile.set_about(metadata.get_about())
195
+ profile.set_display_name(metadata.get_display_name())
196
+ profile.set_name(metadata.get_name())
197
+ profile.set_picture(metadata.get_picture())
198
+ profile.set_website(metadata.get_website())
199
+ return profile
200
+
201
+ def get_private_key(self) -> str:
202
+ return str(self.keys.secret_key().to_bech32())
203
+
204
+ def get_public_key(self) -> str:
205
+ return str(self.keys.public_key().to_bech32())
206
+
207
+ def to_json(self) -> str:
208
+ # Parse parent's JSON string back to dict
209
+ data = json.loads(super().to_json())
210
+ # Add AgentProfile-specific fields
211
+ data.update(
212
+ {
213
+ "profile_url": self.profile_url,
214
+ "public_key": self.keys.public_key().to_bech32(),
215
+ "private_key": self.keys.secret_key().to_bech32(),
216
+ }
217
+ )
218
+ return json.dumps(data)
219
+
220
+
221
+ class MerchantProduct(BaseModel):
222
+ model_config = ConfigDict(arbitrary_types_allowed=True)
223
+
224
+ id: str
225
+ stall_id: str
226
+ name: str
227
+ description: str
228
+ images: List[str]
229
+ currency: str
230
+ price: float
231
+ quantity: int
232
+ shipping: List[ShippingCost]
233
+ categories: List[str] = Field(default_factory=list)
234
+ specs: List[List[str]] = Field(default_factory=list)
235
+
236
+ @classmethod
237
+ def from_product_data(cls, product_data: "ProductData") -> "MerchantProduct":
238
+ # print(f"Raw product data specs: {product_data.specs}") # Debug print
239
+ shipping_costs = []
240
+ for ship in product_data.shipping:
241
+ if isinstance(ship, dict):
242
+ shipping_costs.append(ShippingCost(id=ship["id"], cost=ship["cost"]))
243
+ else:
244
+ shipping_costs.append(ship)
245
+
246
+ # Handle specs - ensure it's a list
247
+ specs = []
248
+ if product_data.specs is not None:
249
+ if isinstance(product_data.specs, dict):
250
+ # Convert dict to list of lists if needed
251
+ specs = [[k, v] for k, v in product_data.specs.items()]
252
+ elif isinstance(product_data.specs, list):
253
+ specs = product_data.specs
254
+
255
+ return cls(
256
+ id=product_data.id,
257
+ stall_id=product_data.stall_id,
258
+ name=product_data.name,
259
+ description=product_data.description,
260
+ images=product_data.images,
261
+ currency=product_data.currency,
262
+ price=product_data.price,
263
+ quantity=product_data.quantity,
264
+ shipping=shipping_costs,
265
+ categories=(
266
+ product_data.categories if product_data.categories is not None else []
267
+ ),
268
+ specs=specs,
269
+ )
270
+
271
+ def to_product_data(self) -> "ProductData":
272
+ try:
273
+ return ProductData(
274
+ id=self.id,
275
+ stall_id=self.stall_id,
276
+ name=self.name,
277
+ description=self.description,
278
+ images=self.images,
279
+ currency=self.currency,
280
+ price=self.price,
281
+ quantity=self.quantity,
282
+ shipping=self.shipping,
283
+ categories=self.categories,
284
+ specs=self.specs,
285
+ )
286
+ except Exception as e:
287
+ logging.error("Failed to convert to ProductData: %s", e)
288
+ logging.error("Shipping data: %s", self.shipping)
289
+ raise
290
+
291
+ def to_dict(self) -> dict:
292
+ """
293
+ Returns a dictionary representation of the MerchantProduct.
294
+ ShippingCost class is not serializable, so we need to convert it
295
+ to a dictionary.
296
+
297
+ Returns:
298
+ dict: dictionary representation of the MerchantProduct
299
+ """
300
+ shipping_dicts = []
301
+ for shipping in self.shipping:
302
+ shipping_dicts.append({"id": shipping.id, "cost": shipping.cost})
303
+
304
+ return {
305
+ "id": self.id,
306
+ "stall_id": self.stall_id,
307
+ "name": self.name,
308
+ "description": self.description,
309
+ "images": self.images,
310
+ "currency": self.currency,
311
+ "price": self.price,
312
+ "quantity": self.quantity,
313
+ "shipping": shipping_dicts,
314
+ "categories": self.categories,
315
+ "specs": self.specs,
316
+ }
317
+
318
+
319
+ class MerchantStall(BaseModel):
320
+ model_config = ConfigDict(arbitrary_types_allowed=True)
321
+
322
+ id: str
323
+ name: str
324
+ description: str
325
+ currency: str
326
+ shipping: List[ShippingMethod]
327
+ geohash: str
328
+
329
+ @classmethod
330
+ def from_stall_data(cls, stall: "StallData") -> "MerchantStall":
331
+ return cls(
332
+ id=stall.id(),
333
+ name=stall.name(),
334
+ description=stall.description(),
335
+ currency=stall.currency(),
336
+ shipping=stall.shipping(),
337
+ )
338
+
339
+ def get_geohash(self) -> str:
340
+ return self.geohash
341
+
342
+ def set_geohash(self, geohash: str) -> None:
343
+ self.geohash = geohash
344
+
345
+ def to_dict(self) -> dict:
346
+ """
347
+ Returns a dictionary representation of the MerchantStall.
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.
351
+
352
+ Returns:
353
+ dict: dictionary representation of the MerchantStall
354
+ """
355
+ shipping_dicts = []
356
+ for shipping in self.shipping:
357
+ shipping_dicts.append(
358
+ {
359
+ "cost": shipping.get_shipping_cost().cost,
360
+ "id": shipping.get_shipping_cost().id,
361
+ }
362
+ )
363
+
364
+ return {
365
+ "id": self.id,
366
+ "name": self.name,
367
+ "description": self.description,
368
+ "currency": self.currency,
369
+ "shipping zones": [shipping_dicts],
370
+ "geohash": self.geohash,
371
+ }
372
+
373
+ def to_stall_data(self) -> "StallData":
374
+ return StallData(
375
+ self.id,
376
+ self.name,
377
+ self.description,
378
+ self.currency,
379
+ self.shipping, # No conversion needed
380
+ )
agentstr/models.pyi ADDED
@@ -0,0 +1,103 @@
1
+ from logging import Logger
2
+ from typing import ClassVar, List, 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: ...