Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): updates to mailbox and agentverse registration process #588

Merged
merged 18 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 37 additions & 26 deletions python/src/uagents/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,21 @@
REGISTRATION_RETRY_INTERVAL_SECONDS,
REGISTRATION_UPDATE_INTERVAL_SECONDS,
TESTNET_PREFIX,
AgentverseConfig,
parse_agentverse_config,
parse_endpoint_config,
)
from uagents.context import Context, ContextFactory, ExternalContext, InternalContext
from uagents.crypto import Identity, derive_key_from_seed, is_user_address
from uagents.dispatch import Sink, dispatcher
from uagents.envelope import EnvelopeHistory, EnvelopeHistoryEntry
from uagents.mailbox import MailboxClient
from uagents.mailbox import (
AgentverseConnectRequest,
MailboxClient,
RegistrationResponse,
is_mailbox_agent,
register_in_agentverse,
)
from uagents.models import ErrorMessage, Model
from uagents.network import (
InsufficientFundsError,
Expand Down Expand Up @@ -212,7 +219,7 @@ class Agent(Sink):
_logger: The logger instance for logging agent activities.
_endpoints (List[AgentEndpoint]): List of endpoints at which the agent is reachable.
_use_mailbox (bool): Indicates if the agent uses a mailbox for communication.
_agentverse (dict): Agentverse configuration settings.
_agentverse (AgentverseConfig): Agentverse configuration settings.
_mailbox_client (MailboxClient): The client for interacting with the agentverse mailbox.
_ledger: The client for interacting with the blockchain ledger.
_almanac_contract: The almanac contract for registering agent addresses to endpoints.
Expand Down Expand Up @@ -326,20 +333,15 @@ def __init__(
else:
agentverse = mailbox
self._agentverse = parse_agentverse_config(agentverse)
self._use_mailbox = self._agentverse["use_mailbox"]
self._use_mailbox = is_mailbox_agent(self._endpoints, self._agentverse)
if self._use_mailbox:
self._mailbox_client = MailboxClient(self, self._logger)
# if mailbox is provided, override endpoints with mailbox endpoint
self._endpoints = [
AgentEndpoint(
url=f"{self.mailbox['http_prefix']}://{self.mailbox['base_url']}/v1/submit",
weight=1,
)
]
self._mailbox_client = MailboxClient(
self._identity, self._agentverse, self._logger
)
else:
self._mailbox_client = None

self._almanac_api_url = f"{self._agentverse['http_prefix']}://{self._agentverse['base_url']}/v1/almanac"
self._almanac_api_url = f"{self._agentverse.url}/v1/almanac"
self._resolver = resolve or GlobalResolver(
max_endpoints=max_resolver_endpoints,
almanac_api_url=self._almanac_api_url,
Expand Down Expand Up @@ -416,6 +418,12 @@ async def _handle_get_info(_ctx: Context):
async def _handle_get_messages(_ctx: Context):
return self._message_cache

@self.on_rest_post("/prove", AgentverseConnectRequest, RegistrationResponse)
async def _handle_prove(_ctx: Context, token: AgentverseConnectRequest):
return await register_in_agentverse(
token, self._identity, self._endpoints, self._agentverse
)

self._enable_agent_inspector = enable_agent_inspector

self._init_done = True
Expand Down Expand Up @@ -596,7 +604,7 @@ def storage(self) -> KeyValueStore:
return self._storage

@property
def mailbox(self) -> Dict[str, str]:
def mailbox(self) -> AgentverseConfig:
"""
Get the mailbox configuration of the agent.
Agentverse overrides it but mailbox is kept for backwards compatibility.
Expand All @@ -607,7 +615,7 @@ def mailbox(self) -> Dict[str, str]:
return self._agentverse

@property
def agentverse(self) -> Dict[str, str]:
def agentverse(self) -> AgentverseConfig:
"""
Get the agentverse configuration of the agent.

Expand Down Expand Up @@ -1027,8 +1035,7 @@ def publish_manifest(self, manifest: Dict[str, Any]):
"""
try:
resp = requests.post(
f"{self._agentverse['http_prefix']}://{self._agentverse['base_url']}"
+ "/v1/almanac/manifests",
f"{self._agentverse.url}/v1/almanac/manifests",
json=manifest,
timeout=5,
)
Expand Down Expand Up @@ -1183,10 +1190,7 @@ async def start_server(self):

"""
if self._enable_agent_inspector:
agentverse_url = (
f"{self._agentverse['http_prefix']}://{self._agentverse['base_url']}"
)
inspector_url = f"{agentverse_url}/inspect/"
inspector_url = f"{self._agentverse.url}/inspect/"
escaped_uri = requests.utils.quote(f"http://127.0.0.1:{self._port}")
self._logger.info(
f"Agent inspector available at {inspector_url}"
Expand Down Expand Up @@ -1358,7 +1362,7 @@ class Bureau:
response Futures.
_logger (Logger): The logger instance.
_server (ASGIServer): The ASGI server instance for handling requests.
_agentverse (Dict[str, str]): The agentverse configuration for the bureau.
_agentverse (AgentverseConfig): The agentverse configuration for the bureau.
_use_mailbox (bool): A flag indicating whether mailbox functionality is enabled for any
of the agents.
_registration_policy (AgentRegistrationPolicy): The registration policy for the bureau.
Expand Down Expand Up @@ -1408,8 +1412,12 @@ def __init__(
logger=self._logger,
)
self._agentverse = parse_agentverse_config(agentverse)
self._use_mailbox = self._agentverse["use_mailbox"]
almanac_api_url = f"{self._agentverse['http_prefix']}://{self._agentverse['base_url']}/v1/almanac"
self._use_mailbox = any(
[
is_mailbox_agent(agent._endpoints, self._agentverse)
for agent in self._agents
]
)
almanac_contract = get_almanac_contract(test)

if wallet and seed:
Expand Down Expand Up @@ -1439,7 +1447,7 @@ def __init__(
almanac_contract,
test,
logger=self._logger,
almanac_api=almanac_api_url,
almanac_api=f"{self._agentverse.url}/v1/almanac",
)

if agents is not None:
Expand All @@ -1456,7 +1464,7 @@ def _update_agent(self, agent: Agent):
"""
agent.update_loop(self._loop)
agent.update_queries(self._queries)
if agent.agentverse["use_mailbox"]:
if is_mailbox_agent(agent._endpoints, self._agentverse):
self._use_mailbox = True
else:
if agent._endpoints:
Expand Down Expand Up @@ -1533,7 +1541,10 @@ async def run_async(self):
return
for agent in self._agents:
await agent.setup()
if agent.agentverse["use_mailbox"] and agent.mailbox_client is not None:
if (
is_mailbox_agent(agent._endpoints, self._agentverse)
and agent.mailbox_client is not None
):
tasks.append(agent.mailbox_client.run())
self._loop.create_task(self._schedule_registration())

Expand Down
9 changes: 7 additions & 2 deletions python/src/uagents/asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

HOST = "0.0.0.0"

RESERVED_ENDPOINTS = ["/submit", "/messages", "/agent_info"]
RESERVED_ENDPOINTS = ["/submit", "/messages", "/agent_info", "/prove"]


async def _read_asgi_body(receive):
Expand Down Expand Up @@ -75,7 +75,7 @@ def __init__(
Tuple[str, RestMethod, str], RestHandlerDetails
] = {}
self._logger = logger or get_logger("server")
self._server = None
self._server: Optional[uvicorn.Server] = None

@property
def server(self):
Expand Down Expand Up @@ -294,6 +294,11 @@ async def __call__(self, scope, receive, send): # pylint: disable=too-many-bra
request_method = scope["method"]
request_path = scope["path"]

# Handle OPTIONS preflight request for CORS
if request_method == "OPTIONS":
await self._asgi_send(send, 204)
return

# check if the request is for a REST endpoint
handlers = self._get_rest_handler_details(request_method, request_path)
if handlers:
Expand Down
40 changes: 25 additions & 15 deletions python/src/uagents/config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from typing import Dict, List, Optional, Union
from typing import Dict, List, Literal, Optional, Union

from pydantic import BaseModel

from uagents.types import AgentEndpoint

Expand Down Expand Up @@ -43,6 +45,19 @@
DEFAULT_SEARCH_LIMIT = 100


AgentType = Literal["hosted", "local", "mailbox", "proxy", "custom"]


class AgentverseConfig(BaseModel):
base_url: str
protocol: str
http_prefix: str

@property
def url(self) -> str:
return f"{self.http_prefix}://{self.base_url}"


def parse_endpoint_config(
endpoint: Optional[Union[str, List[str], Dict[str, dict]]],
) -> List[AgentEndpoint]:
Expand Down Expand Up @@ -72,36 +87,31 @@ def parse_endpoint_config(

def parse_agentverse_config(
config: Optional[Union[str, Dict[str, str]]] = None,
) -> Dict[str, Union[str, bool, None]]:
) -> AgentverseConfig:
"""
Parse the user-provided agentverse configuration.

Returns:
Dict[str, Union[str, bool, None]]: The parsed agentverse configuration.
AgentverseConfig: The parsed agentverse configuration.
"""
agent_mailbox_key = None
base_url = AGENTVERSE_URL
protocol = None
protocol_override = None
if isinstance(config, str):
if config.count("@") == 1:
agent_mailbox_key, base_url = config.split("@")
_, base_url = config.split("@")
elif "://" in config:
base_url = config
else:
agent_mailbox_key = config
elif isinstance(config, dict):
agent_mailbox_key = config.get("agent_mailbox_key")
base_url = config.get("base_url") or base_url
protocol_override = config.get("protocol")
if "://" in base_url:
protocol, base_url = base_url.split("://")
protocol = protocol_override or protocol or "https"
http_prefix = "https" if protocol in {"wss", "https"} else "http"
return {
"agent_mailbox_key": agent_mailbox_key,
"base_url": base_url,
"protocol": protocol,
"http_prefix": http_prefix,
"use_mailbox": agent_mailbox_key is not None,
}

return AgentverseConfig(
base_url=base_url,
protocol=protocol,
http_prefix=http_prefix,
)
Loading
Loading