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/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: ...