uagents-core 0.2.2__tar.gz → 0.3.0__tar.gz
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.
- {uagents_core-0.2.2 → uagents_core-0.3.0}/PKG-INFO +1 -1
- {uagents_core-0.2.2 → uagents_core-0.3.0}/pyproject.toml +1 -1
- {uagents_core-0.2.2 → uagents_core-0.3.0}/uagents_core/config.py +5 -0
- uagents_core-0.3.0/uagents_core/storage.py +135 -0
- {uagents_core-0.2.2 → uagents_core-0.3.0}/uagents_core/utils/messages.py +1 -1
- {uagents_core-0.2.2 → uagents_core-0.3.0}/uagents_core/utils/registration.py +6 -7
- {uagents_core-0.2.2 → uagents_core-0.3.0}/README.md +0 -0
- {uagents_core-0.2.2 → uagents_core-0.3.0}/uagents_core/__init__.py +0 -0
- {uagents_core-0.2.2 → uagents_core-0.3.0}/uagents_core/contrib/__init__.py +0 -0
- {uagents_core-0.2.2 → uagents_core-0.3.0}/uagents_core/contrib/protocols/__init__.py +0 -0
- {uagents_core-0.2.2 → uagents_core-0.3.0}/uagents_core/contrib/protocols/chat/__init__.py +0 -0
- {uagents_core-0.2.2 → uagents_core-0.3.0}/uagents_core/contrib/protocols/subscriptions/__init__.py +0 -0
- {uagents_core-0.2.2 → uagents_core-0.3.0}/uagents_core/envelope.py +0 -0
- {uagents_core-0.2.2 → uagents_core-0.3.0}/uagents_core/helpers.py +0 -0
- {uagents_core-0.2.2 → uagents_core-0.3.0}/uagents_core/identity.py +0 -0
- {uagents_core-0.2.2 → uagents_core-0.3.0}/uagents_core/logger.py +0 -0
- {uagents_core-0.2.2 → uagents_core-0.3.0}/uagents_core/models.py +0 -0
- {uagents_core-0.2.2 → uagents_core-0.3.0}/uagents_core/protocol.py +0 -0
- {uagents_core-0.2.2 → uagents_core-0.3.0}/uagents_core/registration.py +0 -0
- {uagents_core-0.2.2 → uagents_core-0.3.0}/uagents_core/types.py +0 -0
- {uagents_core-0.2.2 → uagents_core-0.3.0}/uagents_core/utils/__init__.py +0 -0
- {uagents_core-0.2.2 → uagents_core-0.3.0}/uagents_core/utils/resolver.py +0 -0
@@ -6,6 +6,7 @@ DEFAULT_REGISTRATION_PATH = "/v1/agents"
|
|
6
6
|
DEFAULT_CHALLENGE_PATH = "/v1/auth/challenge"
|
7
7
|
DEFAULT_MAILBOX_PATH = "/v1/submit"
|
8
8
|
DEFAULT_PROXY_PATH = "/v1/proxy/submit"
|
9
|
+
DEFAULT_STORAGE_PATH = "/v1/storage"
|
9
10
|
|
10
11
|
DEFAULT_MAX_ENDPOINTS = 10
|
11
12
|
|
@@ -30,3 +31,7 @@ class AgentverseConfig(BaseModel):
|
|
30
31
|
@property
|
31
32
|
def proxy_endpoint(self) -> str:
|
32
33
|
return f"{self.url}{DEFAULT_PROXY_PATH}"
|
34
|
+
|
35
|
+
@property
|
36
|
+
def storage_endpoint(self) -> str:
|
37
|
+
return f"{self.url}{DEFAULT_STORAGE_PATH}"
|
@@ -0,0 +1,135 @@
|
|
1
|
+
import base64
|
2
|
+
import struct
|
3
|
+
from datetime import datetime
|
4
|
+
from secrets import token_bytes
|
5
|
+
from typing import Optional
|
6
|
+
|
7
|
+
import requests
|
8
|
+
|
9
|
+
from uagents_core.config import AgentverseConfig
|
10
|
+
from uagents_core.identity import Identity
|
11
|
+
|
12
|
+
|
13
|
+
def compute_attestation(
|
14
|
+
identity: Identity, validity_start: datetime, validity_secs: int, nonce: bytes
|
15
|
+
) -> str:
|
16
|
+
"""
|
17
|
+
Compute a valid agent attestation token for authentication.
|
18
|
+
"""
|
19
|
+
assert len(nonce) == 32, "Nonce is of invalid length"
|
20
|
+
|
21
|
+
valid_from = int(validity_start.timestamp())
|
22
|
+
valid_to = valid_from + validity_secs
|
23
|
+
|
24
|
+
public_key = bytes.fromhex(identity.pub_key)
|
25
|
+
|
26
|
+
payload = public_key + struct.pack(">QQ", valid_from, valid_to) + nonce
|
27
|
+
assert len(payload) == 81, "attestation payload is incorrect"
|
28
|
+
|
29
|
+
signature = identity.sign(payload)
|
30
|
+
attestation = f"attr:{base64.b64encode(payload).decode()}:{signature}"
|
31
|
+
return attestation
|
32
|
+
|
33
|
+
|
34
|
+
class ExternalStorage:
|
35
|
+
def __init__(
|
36
|
+
self,
|
37
|
+
identity: Optional[Identity] = None,
|
38
|
+
storage_url: Optional[str] = None,
|
39
|
+
api_token: Optional[str] = None,
|
40
|
+
):
|
41
|
+
self.identity = identity
|
42
|
+
self.api_token = api_token
|
43
|
+
self.storage_url = storage_url or AgentverseConfig().storage_endpoint
|
44
|
+
|
45
|
+
def _make_attestation(self) -> str:
|
46
|
+
nonce = token_bytes(32)
|
47
|
+
now = datetime.now()
|
48
|
+
return compute_attestation(self.identity, now, 3600, nonce)
|
49
|
+
|
50
|
+
def _get_auth_header(self) -> dict:
|
51
|
+
if self.api_token:
|
52
|
+
return {"Authorization": f"Bearer {self.api_token}"}
|
53
|
+
elif self.identity:
|
54
|
+
return {"Authorization": f"Agent {self._make_attestation()}"}
|
55
|
+
else:
|
56
|
+
raise RuntimeError("No identity or API token available for authentication")
|
57
|
+
|
58
|
+
def upload(
|
59
|
+
self, asset_id: str, asset_content: str, mime_type: str = "text/plain"
|
60
|
+
) -> dict:
|
61
|
+
url = f"{self.storage_url}/assets/{asset_id}/contents/"
|
62
|
+
headers = self._get_auth_header()
|
63
|
+
headers["Content-Type"] = "application/json"
|
64
|
+
payload = {
|
65
|
+
"contents": base64.b64encode(asset_content.encode()).decode(),
|
66
|
+
"mime_type": mime_type,
|
67
|
+
}
|
68
|
+
response = requests.put(url, json=payload, headers=headers)
|
69
|
+
if response.status_code != 200:
|
70
|
+
raise RuntimeError(
|
71
|
+
f"Upload failed: {response.status_code}, {response.text}"
|
72
|
+
)
|
73
|
+
|
74
|
+
return response.json()
|
75
|
+
|
76
|
+
def download(self, asset_id: str) -> dict:
|
77
|
+
url = f"{self.storage_url}/assets/{asset_id}/contents/"
|
78
|
+
headers = self._get_auth_header()
|
79
|
+
|
80
|
+
response = requests.get(url, headers=headers)
|
81
|
+
if response.status_code != 200:
|
82
|
+
raise RuntimeError(
|
83
|
+
f"Download failed: {response.status_code}, {response.text}"
|
84
|
+
)
|
85
|
+
|
86
|
+
return response.json()
|
87
|
+
|
88
|
+
def create_asset(
|
89
|
+
self,
|
90
|
+
name: str,
|
91
|
+
content: str,
|
92
|
+
mime_type: str = "text/plain",
|
93
|
+
lifetime_hours: int = 24,
|
94
|
+
) -> str:
|
95
|
+
if not self.api_token:
|
96
|
+
raise RuntimeError("API token required to create assets")
|
97
|
+
url = f"{self.storage_url}/assets/"
|
98
|
+
headers = self._get_auth_header()
|
99
|
+
headers["Content-Type"] = "application/json"
|
100
|
+
payload = {
|
101
|
+
"name": name,
|
102
|
+
"mime_type": mime_type,
|
103
|
+
"contents": base64.b64encode(content.encode()).decode(),
|
104
|
+
"lifetime_hours": lifetime_hours,
|
105
|
+
}
|
106
|
+
|
107
|
+
response = requests.post(url, json=payload, headers=headers)
|
108
|
+
if response.status_code != 201:
|
109
|
+
raise RuntimeError(
|
110
|
+
f"Asset creation failed: {response.status_code}, {response.text}"
|
111
|
+
)
|
112
|
+
|
113
|
+
return response.json()["asset_id"]
|
114
|
+
|
115
|
+
def set_permissions(
|
116
|
+
self, asset_id: str, agent_address: str, read: bool = True, write: bool = True
|
117
|
+
):
|
118
|
+
if not self.api_token:
|
119
|
+
raise RuntimeError("API token required to set permissions")
|
120
|
+
url = f"{self.storage_url}/assets/{asset_id}/permissions/"
|
121
|
+
headers = self._get_auth_header()
|
122
|
+
headers["Content-Type"] = "application/json"
|
123
|
+
payload = {
|
124
|
+
"agent_address": agent_address,
|
125
|
+
"read": read,
|
126
|
+
"write": write,
|
127
|
+
}
|
128
|
+
|
129
|
+
response = requests.put(url, json=payload, headers=headers)
|
130
|
+
if response.status_code != 200:
|
131
|
+
raise RuntimeError(
|
132
|
+
f"Set permissions failed: {response.status_code}, {response.text}"
|
133
|
+
)
|
134
|
+
|
135
|
+
return response.json()
|
@@ -51,8 +51,11 @@ def _send_post_request(
|
|
51
51
|
response.raise_for_status()
|
52
52
|
return True, response
|
53
53
|
except requests.RequestException as e:
|
54
|
+
error_detail = getattr(e, "response", None)
|
55
|
+
if error_detail is not None:
|
56
|
+
error_detail = error_detail.text
|
54
57
|
logger.error(
|
55
|
-
msg="Error submitting request",
|
58
|
+
msg=f"Error submitting request: {error_detail}",
|
56
59
|
extra={"url": url, "data": data.model_dump_json()},
|
57
60
|
exc_info=e,
|
58
61
|
)
|
@@ -63,6 +66,7 @@ def register_in_almanac(
|
|
63
66
|
identity: Identity,
|
64
67
|
endpoints: list[str],
|
65
68
|
protocol_digests: list[str],
|
69
|
+
metadata: dict[str, str | dict[str, str]] | None = None,
|
66
70
|
*,
|
67
71
|
agentverse_config: AgentverseConfig | None = None,
|
68
72
|
timeout: int = DEFAULT_REQUEST_TIMEOUT,
|
@@ -115,7 +119,7 @@ def register_in_almanac(
|
|
115
119
|
agent_identifier=agent_address,
|
116
120
|
protocols=protocol_digests,
|
117
121
|
endpoints=agent_endpoints,
|
118
|
-
metadata=
|
122
|
+
metadata=metadata,
|
119
123
|
)
|
120
124
|
|
121
125
|
logger.info(msg="Registering with Almanac API", extra=attestation.model_dump())
|
@@ -220,11 +224,6 @@ def register_in_agentverse(
|
|
220
224
|
extra=registration_metadata,
|
221
225
|
)
|
222
226
|
return False
|
223
|
-
if response.status_code == 409:
|
224
|
-
logger.info(
|
225
|
-
msg="Agent already registered with Agentverse",
|
226
|
-
extra=registration_metadata,
|
227
|
-
)
|
228
227
|
else:
|
229
228
|
registration_response = RegistrationResponse.model_validate_json(
|
230
229
|
response.text
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{uagents_core-0.2.2 → uagents_core-0.3.0}/uagents_core/contrib/protocols/subscriptions/__init__.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|