From 11238add001f504c73e2453c87e4c8de2bd6f0dc Mon Sep 17 00:00:00 2001
From: James Riehl <33920192+jrriehl@users.noreply.github.com>
Date: Thu, 12 Dec 2024 12:06:14 +0000
Subject: [PATCH] feat(core): updates to mailbox and agentverse registration
process (#588)
---
python/docs/api/uagents/agent.md | 47 +--
python/docs/api/uagents/config.md | 24 +-
python/docs/api/uagents/mailbox.md | 89 ++---
python/examples/11-mailbox-agents/alice.py | 18 +-
python/examples/11-mailbox-agents/bob.py | 15 +-
python/examples/12-remote-agents/agent1.py | 1 -
.../17-stateful-communication/agent1.py | 1 -
.../17-stateful-communication/agent2.py | 1 -
.../17-stateful-communication/agent3.py | 1 -
.../17-stateful-communication/agent4.py | 1 -
python/src/uagents/agent.py | 116 +++---
python/src/uagents/asgi.py | 35 +-
python/src/uagents/config.py | 73 +++-
python/src/uagents/mailbox.py | 369 +++++++++++-------
python/tests/test_config.py | 102 +++--
15 files changed, 468 insertions(+), 425 deletions(-)
diff --git a/python/docs/api/uagents/agent.md b/python/docs/api/uagents/agent.md
index 9fe67330..7cccfa2a 100644
--- a/python/docs/api/uagents/agent.md
+++ b/python/docs/api/uagents/agent.md
@@ -130,7 +130,7 @@ An agent that interacts within a communication environment.
- `_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.
@@ -168,8 +168,7 @@ An agent that interacts within a communication environment.
- `identifier` _str_ - The Agent Identifier, including network prefix and address.
- `wallet` _LocalWallet_ - The agent's wallet for transacting on the ledger.
- `storage` _KeyValueStore_ - The key-value store for storage operations.
-- `mailbox` _Dict[str, str]_ - The mailbox configuration for the agent.
-- `agentverse` _Dict[str, str]_ - The agentverse configuration for the agent.
+- `agentverse` _AgentverseConfig_ - The agentverse configuration for the agent.
- `mailbox_client` _MailboxClient_ - The client for interacting with the agentverse mailbox.
- `protocols` _Dict[str, Protocol]_ - Dictionary mapping all supported protocol digests to their
corresponding protocols.
@@ -185,7 +184,8 @@ def __init__(name: Optional[str] = None,
seed: Optional[str] = None,
endpoint: Optional[Union[str, List[str], Dict[str, dict]]] = None,
agentverse: Optional[Union[str, Dict[str, str]]] = None,
- mailbox: Optional[Union[str, Dict[str, str]]] = None,
+ mailbox: bool = False,
+ proxy: bool = False,
resolve: Optional[Resolver] = None,
registration_policy: Optional[AgentRegistrationPolicy] = None,
enable_wallet_messaging: Union[bool, Dict[str, str]] = False,
@@ -208,7 +208,8 @@ Initialize an Agent instance.
- `seed` _Optional[str]_ - The seed for generating keys.
- `endpoint` _Optional[Union[str, List[str], Dict[str, dict]]]_ - The endpoint configuration.
- `agentverse` _Optional[Union[str, Dict[str, str]]]_ - The agentverse configuration.
-- `mailbox` _Optional[Union[str, Dict[str, str]]]_ - The mailbox configuration.
+- `mailbox` _bool_ - True if the agent will receive messages via an Agentverse mailbox.
+- `proxy` _bool_ - True if the agent will receive messages via an Agentverse proxy endpoint.
- `resolve` _Optional[Resolver]_ - The resolver to use for agent communication.
- `enable_wallet_messaging` _Optional[Union[bool, Dict[str, str]]]_ - Whether to enable
wallet messaging. If '{"chain_id": CHAIN_ID}' is provided, this sets the chain ID for
@@ -328,29 +329,13 @@ Get the key-value store used by the agent for data storage.
- `KeyValueStore` - The key-value store instance.
-
-
-#### mailbox
-
-```python
-@property
-def mailbox() -> Dict[str, str]
-```
-
-Get the mailbox configuration of the agent.
-Agentverse overrides it but mailbox is kept for backwards compatibility.
-
-**Returns**:
-
- Dict[str, str]: The mailbox configuration.
-
#### agentverse
```python
@property
-def agentverse() -> Dict[str, str]
+def agentverse() -> AgentverseConfig
```
Get the agentverse configuration of the agent.
@@ -419,22 +404,6 @@ Get the metadata associated with the agent.
Dict[str, Any]: The metadata associated with the agent.
-
-
-#### mailbox
-
-```python
-@mailbox.setter
-def mailbox(config: Union[str, Dict[str, str]])
-```
-
-Set the mailbox configuration for the agent.
-Agentverse overrides it but mailbox is kept for backwards compatibility.
-
-**Arguments**:
-
-- `config` _Union[str, Dict[str, str]]_ - The new mailbox configuration.
-
#### agentverse
@@ -863,7 +832,7 @@ This class manages a collection of agents and orchestrates their execution.
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.
diff --git a/python/docs/api/uagents/config.md b/python/docs/api/uagents/config.md
index 48bda955..882e5de2 100644
--- a/python/docs/api/uagents/config.md
+++ b/python/docs/api/uagents/config.md
@@ -8,15 +8,27 @@
```python
def parse_endpoint_config(
- endpoint: Optional[Union[str, List[str], Dict[str, dict]]]
-) -> List[AgentEndpoint]
+ endpoint: Optional[Union[str, List[str], Dict[str, dict]]],
+ agentverse: AgentverseConfig,
+ mailbox: bool = False,
+ proxy: bool = False,
+ logger: Optional[logging.Logger] = None) -> List[AgentEndpoint]
```
Parse the user-provided endpoint configuration.
+**Arguments**:
+
+- `endpoint` _Optional[Union[str, List[str], Dict[str, dict]]]_ - The endpoint configuration.
+- `agentverse` _AgentverseConfig_ - The agentverse configuration.
+- `mailbox` _bool_ - Whether to use the mailbox endpoint.
+- `proxy` _bool_ - Whether to use the proxy endpoint.
+- `logger` _Optional[logging.Logger]_ - The logger to use.
+
+
**Returns**:
- Optional[List[Dict[str, Any]]]: The parsed endpoint configuration.
+- `Optional[List[AgentEndpoint]` - The parsed endpoint configuration.
@@ -24,13 +36,13 @@ Parse the user-provided endpoint configuration.
```python
def parse_agentverse_config(
- config: Optional[Union[str, Dict[str, str]]] = None
-) -> Dict[str, Union[str, bool, None]]
+ config: Optional[Union[str, Dict[str,
+ str]]] = None) -> AgentverseConfig
```
Parse the user-provided agentverse configuration.
**Returns**:
- Dict[str, Union[str, bool, None]]: The parsed agentverse configuration.
+- `AgentverseConfig` - The parsed agentverse configuration.
diff --git a/python/docs/api/uagents/mailbox.md b/python/docs/api/uagents/mailbox.md
index c74e71e4..7a3320f0 100644
--- a/python/docs/api/uagents/mailbox.md
+++ b/python/docs/api/uagents/mailbox.md
@@ -2,67 +2,70 @@
# src.uagents.mailbox
-
+
-## MailboxClient Objects
+#### is`_`mailbox`_`agent
```python
-class MailboxClient()
+def is_mailbox_agent(endpoints: list[AgentEndpoint],
+ agentverse: AgentverseConfig) -> bool
```
-Client for interacting with the Agentverse mailbox server.
+Check if the agent is a mailbox agent.
-
+**Returns**:
-#### base`_`url
+- `bool` - True if the agent is a mailbox agent, False otherwise.
+
+
+
+#### is`_`proxy`_`agent
```python
-@property
-def base_url()
+def is_proxy_agent(endpoints: list[AgentEndpoint],
+ agentverse: AgentverseConfig) -> bool
```
-Property to access the base url of the mailbox server.
+Check if the agent is a proxy agent.
+
+**Returns**:
-Returns: The base url of the mailbox server.
+- `bool` - True if the agent is a proxy agent, False otherwise.
-
+
-#### agent`_`mailbox`_`key
+#### register`_`in`_`agentverse
```python
-@property
-def agent_mailbox_key()
+async def register_in_agentverse(
+ request: AgentverseConnectRequest, identity: Identity,
+ endpoints: list[AgentEndpoint],
+ agentverse: AgentverseConfig) -> RegistrationResponse
```
-Property to access the agent_mailbox_key of the mailbox server.
+Registers agent in Agentverse
-Returns: The agent_mailbox_key of the mailbox server.
+**Arguments**:
-
+- `request` _AgentverseConnectRequest_ - Request object
+- `identity` _Identity_ - Agent identity object
+- `endpoints` _list[AgentEndpoint]_ - Endpoints of the agent
+- `agentverse` _AgentverseConfig_ - Agentverse configuration
+
-#### protocol
+**Returns**:
-```python
-@property
-def protocol()
-```
-
-Property to access the protocol of the mailbox server.
-
-Returns: The protocol of the mailbox server {ws, wss, http, https}.
+- `RegistrationResponse` - Registration
-
+
-#### http`_`prefix
+## MailboxClient Objects
```python
-@property
-def http_prefix()
+class MailboxClient()
```
-Property to access the http prefix of the mailbox server.
-
-Returns: The http prefix of the mailbox server {http, https}.
+Client for interacting with the Agentverse mailbox server.
@@ -74,23 +77,3 @@ async def run()
Runs the mailbox client.
-
-
-#### start`_`polling
-
-```python
-async def start_polling()
-```
-
-Runs the mailbox client. Acquires an access token if needed and then starts a polling loop.
-
-
-
-#### process`_`deletion`_`queue
-
-```python
-async def process_deletion_queue()
-```
-
-Processes the deletion queue. Deletes envelopes from the mailbox server.
-
diff --git a/python/examples/11-mailbox-agents/alice.py b/python/examples/11-mailbox-agents/alice.py
index 60c2fc46..8c0107c4 100644
--- a/python/examples/11-mailbox-agents/alice.py
+++ b/python/examples/11-mailbox-agents/alice.py
@@ -5,25 +5,11 @@ class Message(Model):
message: str
-# First generate a secure seed phrase (e.g. https://pypi.org/project/mnemonic/)
-SEED_PHRASE = "put_your_seed_phrase_here"
-
-# Copy the address shown below
-print(f"Your agent's address is: {Agent(seed=SEED_PHRASE).address}")
-
-# Then go to https://agentverse.ai, register your agent in the Mailroom
-# and copy the agent's mailbox key
-AGENT_MAILBOX_KEY = "put_your_AGENT_MAILBOX_KEY_here"
-
# Now your agent is ready to join the agentverse!
-agent = Agent(
- name="alice",
- seed=SEED_PHRASE,
- mailbox=f"{AGENT_MAILBOX_KEY}@https://agentverse.ai",
-)
+agent = Agent(name="alice", port=8008, mailbox=True)
-@agent.on_message(model=Message, replies={Message})
+@agent.on_message(model=Message, replies=Message)
async def handle_message(ctx: Context, sender: str, msg: Message):
ctx.logger.info(f"Received message from {sender}: {msg.message}")
diff --git a/python/examples/11-mailbox-agents/bob.py b/python/examples/11-mailbox-agents/bob.py
index 72a50930..d3c1c62b 100644
--- a/python/examples/11-mailbox-agents/bob.py
+++ b/python/examples/11-mailbox-agents/bob.py
@@ -8,22 +8,9 @@ class Message(Model):
# Copy ALICE_ADDRESS generated in alice.py
ALICE_ADDRESS = "paste_alice_address_here"
-# Generate a second seed phrase (e.g. https://pypi.org/project/mnemonic/)
-SEED_PHRASE = "put_your_seed_phrase_here"
-
-# Copy the address shown below
-print(f"Your agent's address is: {Agent(seed=SEED_PHRASE).address}")
-
-# Then go to https://agentverse.ai, register your agent in the Mailroom
-# and copy the agent's mailbox key
-AGENT_MAILBOX_KEY = "put_your_AGENT_MAILBOX_KEY_here"
# Now your agent is ready to join the agentverse!
-agent = Agent(
- name="bob",
- seed=SEED_PHRASE,
- mailbox=f"{AGENT_MAILBOX_KEY}@https://agentverse.ai",
-)
+agent = Agent(name="bob", port=8009, mailbox=True)
@agent.on_interval(period=2.0)
diff --git a/python/examples/12-remote-agents/agent1.py b/python/examples/12-remote-agents/agent1.py
index 0fc3e4e0..8d27df38 100644
--- a/python/examples/12-remote-agents/agent1.py
+++ b/python/examples/12-remote-agents/agent1.py
@@ -36,7 +36,6 @@ async def act_on_message(ctx: Context, sender: str, msg: Message):
ctx.logger.info(f"Received message from {sender[-8:]}: {msg.message}")
-print(f"Agent address: {alice.address}")
print(f"Agent public URL: {http_tunnel.public_url}/submit")
if __name__ == "__main__":
diff --git a/python/examples/17-stateful-communication/agent1.py b/python/examples/17-stateful-communication/agent1.py
index 0f6c3dcb..f3091e05 100644
--- a/python/examples/17-stateful-communication/agent1.py
+++ b/python/examples/17-stateful-communication/agent1.py
@@ -121,5 +121,4 @@ async def conclude_chitchat(
if __name__ == "__main__":
- print(f"Agent address: {agent.address}")
agent.run()
diff --git a/python/examples/17-stateful-communication/agent2.py b/python/examples/17-stateful-communication/agent2.py
index f4af3b19..ec07b9f1 100644
--- a/python/examples/17-stateful-communication/agent2.py
+++ b/python/examples/17-stateful-communication/agent2.py
@@ -120,5 +120,4 @@ async def start_cycle(ctx: Context):
if __name__ == "__main__":
- print(f"Agent address: {agent.address}")
agent.run()
diff --git a/python/examples/17-stateful-communication/agent3.py b/python/examples/17-stateful-communication/agent3.py
index f90489ee..187ae3a6 100644
--- a/python/examples/17-stateful-communication/agent3.py
+++ b/python/examples/17-stateful-communication/agent3.py
@@ -54,5 +54,4 @@ async def continue_chitchat(
if __name__ == "__main__":
- print(f"Agent address: {agent.address}")
agent.run()
diff --git a/python/examples/17-stateful-communication/agent4.py b/python/examples/17-stateful-communication/agent4.py
index e3c0d68a..d12d2cb6 100644
--- a/python/examples/17-stateful-communication/agent4.py
+++ b/python/examples/17-stateful-communication/agent4.py
@@ -49,5 +49,4 @@ async def start_cycle(ctx: Context):
agent.include(chitchat_dialogue)
if __name__ == "__main__":
- print(f"Agent address: {agent.address}")
agent.run()
diff --git a/python/src/uagents/agent.py b/python/src/uagents/agent.py
index b0eae87a..95711930 100644
--- a/python/src/uagents/agent.py
+++ b/python/src/uagents/agent.py
@@ -32,6 +32,7 @@
REGISTRATION_RETRY_INTERVAL_SECONDS,
REGISTRATION_UPDATE_INTERVAL_SECONDS,
TESTNET_PREFIX,
+ AgentverseConfig,
parse_agentverse_config,
parse_endpoint_config,
)
@@ -39,7 +40,13 @@
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,
@@ -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.
@@ -250,8 +257,7 @@ class Agent(Sink):
identifier (str): The Agent Identifier, including network prefix and address.
wallet (LocalWallet): The agent's wallet for transacting on the ledger.
storage (KeyValueStore): The key-value store for storage operations.
- mailbox (Dict[str, str]): The mailbox configuration for the agent.
- agentverse (Dict[str, str]): The agentverse configuration for the agent.
+ agentverse (AgentverseConfig): The agentverse configuration for the agent.
mailbox_client (MailboxClient): The client for interacting with the agentverse mailbox.
protocols (Dict[str, Protocol]): Dictionary mapping all supported protocol digests to their
corresponding protocols.
@@ -266,7 +272,8 @@ def __init__(
seed: Optional[str] = None,
endpoint: Optional[Union[str, List[str], Dict[str, dict]]] = None,
agentverse: Optional[Union[str, Dict[str, str]]] = None,
- mailbox: Optional[Union[str, Dict[str, str]]] = None,
+ mailbox: bool = False,
+ proxy: bool = False,
resolve: Optional[Resolver] = None,
registration_policy: Optional[AgentRegistrationPolicy] = None,
enable_wallet_messaging: Union[bool, Dict[str, str]] = False,
@@ -288,7 +295,8 @@ def __init__(
seed (Optional[str]): The seed for generating keys.
endpoint (Optional[Union[str, List[str], Dict[str, dict]]]): The endpoint configuration.
agentverse (Optional[Union[str, Dict[str, str]]]): The agentverse configuration.
- mailbox (Optional[Union[str, Dict[str, str]]]): The mailbox configuration.
+ mailbox (bool): True if the agent will receive messages via an Agentverse mailbox.
+ proxy (bool): True if the agent will receive messages via an Agentverse proxy endpoint.
resolve (Optional[Resolver]): The resolver to use for agent communication.
enable_wallet_messaging (Optional[Union[bool, Dict[str, str]]]): Whether to enable
wallet messaging. If '{"chain_id": CHAIN_ID}' is provided, this sets the chain ID for
@@ -312,34 +320,22 @@ def __init__(
self._initialize_wallet_and_identity(seed, name, wallet_key_derivation_index)
self._logger = get_logger(self.name, level=log_level)
+ self._agentverse = parse_agentverse_config(agentverse)
+
# configure endpoints and mailbox
- self._endpoints = parse_endpoint_config(endpoint)
- self._use_mailbox = False
+ self._endpoints = parse_endpoint_config(
+ endpoint, self._agentverse, mailbox, proxy, self._logger
+ )
- if mailbox:
- # agentverse config overrides mailbox config
- # but mailbox is kept for backwards compatibility
- if agentverse:
- self._logger.warning(
- "Ignoring the provided 'mailbox' configuration since 'agentverse' overrides it"
- )
- 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,
@@ -416,6 +412,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
@@ -596,18 +598,7 @@ def storage(self) -> KeyValueStore:
return self._storage
@property
- def mailbox(self) -> Dict[str, str]:
- """
- Get the mailbox configuration of the agent.
- Agentverse overrides it but mailbox is kept for backwards compatibility.
-
- Returns:
- Dict[str, str]: The mailbox configuration.
- """
- return self._agentverse
-
- @property
- def agentverse(self) -> Dict[str, str]:
+ def agentverse(self) -> AgentverseConfig:
"""
Get the agentverse configuration of the agent.
@@ -662,17 +653,6 @@ def metadata(self) -> Dict[str, Any]:
"""
return self._metadata
- @mailbox.setter
- def mailbox(self, config: Union[str, Dict[str, str]]):
- """
- Set the mailbox configuration for the agent.
- Agentverse overrides it but mailbox is kept for backwards compatibility.
-
- Args:
- config (Union[str, Dict[str, str]]): The new mailbox configuration.
- """
- self._agentverse = parse_agentverse_config(config)
-
@agentverse.setter
def agentverse(self, config: Union[str, Dict[str, str]]):
"""
@@ -919,7 +899,7 @@ def _on_rest(
return lambda func: func
def decorator_on_rest(func: RestHandler):
- @functools.wraps(RestGetHandler if method == "GET" else RestPostHandler)
+ @functools.wraps(RestGetHandler if method == "GET" else RestPostHandler) # type: ignore
def handler(*args, **kwargs):
return func(*args, **kwargs)
@@ -1027,8 +1007,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,
)
@@ -1084,6 +1063,7 @@ async def _startup(self):
Perform startup actions.
"""
+ self._logger.info(f"Starting agent with address: {self.address}")
if self._registration_policy:
if self._endpoints:
self.start_registration_loop()
@@ -1183,10 +1163,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}"
@@ -1358,7 +1335,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.
@@ -1397,7 +1374,6 @@ def __init__(
"""
self._loop = loop or asyncio.get_event_loop_policy().get_event_loop()
self._agents: List[Agent] = []
- self._endpoints = parse_endpoint_config(endpoint)
self._port = port or 8000
self._queries: Dict[str, asyncio.Future] = {}
self._logger = get_logger("bureau", log_level)
@@ -1408,8 +1384,15 @@ 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._endpoints = parse_endpoint_config(
+ endpoint, self._agentverse, False, False, self._logger
+ )
+ 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:
@@ -1439,7 +1422,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:
@@ -1456,7 +1439,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:
@@ -1533,7 +1516,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())
diff --git a/python/src/uagents/asgi.py b/python/src/uagents/asgi.py
index d3d14814..899f8962 100644
--- a/python/src/uagents/asgi.py
+++ b/python/src/uagents/asgi.py
@@ -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):
@@ -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):
@@ -133,19 +133,27 @@ async def _asgi_send(
body: Optional[Union[Dict[str, Any], ErrorWrapper]] = None,
):
header = (
- [[k.encode(), v.encode()] for k, v in headers.items()] if headers else None
+ [[k.encode(), v.encode()] for k, v in headers.items()]
+ if headers is not None
+ else [[b"content-type", b"application/json"]]
)
- if body is None:
- body = {}
await send(
{
"type": "http.response.start",
"status": status_code,
- "headers": header or [[b"content-type", b"application/json"]],
+ "headers": header,
}
)
- await send({"type": "http.response.body", "body": json.dumps(body).encode()})
+
+ if body is None:
+ encoded_body = (
+ b"{}" if [[b"content-type", b"application/json"]] in header else b""
+ )
+ else:
+ encoded_body = json.dumps(body).encode()
+
+ await send({"type": "http.response.body", "body": encoded_body})
async def handle_readiness_probe(self, headers: CaseInsensitiveDict, send):
"""
@@ -154,7 +162,7 @@ async def handle_readiness_probe(self, headers: CaseInsensitiveDict, send):
if b"x-uagents-address" not in headers:
await self._asgi_send(send, headers={"x-uagents-status": "indeterminate"})
else:
- address = headers[b"x-uagents-address"].decode()
+ address = headers[b"x-uagents-address"].decode() # type: ignore
if not dispatcher.contains(address):
await self._asgi_send(send, headers={"x-uagents-status": "not-ready"})
else:
@@ -224,7 +232,7 @@ async def _handle_rest(
},
)
return
- destination = headers[b"x-uagents-address"].decode()
+ destination = headers[b"x-uagents-address"].decode() # type: ignore
rest_handler = handlers.get(destination)
else:
destination, rest_handler = handlers.popitem()
@@ -294,6 +302,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, headers={})
+ return
+
# check if the request is for a REST endpoint
handlers = self._get_rest_handler_details(request_method, request_path)
if handlers:
@@ -316,7 +329,7 @@ async def __call__(self, scope, receive, send): # pylint: disable=too-many-bra
await self.handle_missing_content_type(headers, send)
return
- if b"application/json" not in headers[b"content-type"]:
+ if b"application/json" not in headers[b"content-type"]: # type: ignore
await self._asgi_send(send, 400, body={"error": "invalid content-type"})
return
@@ -337,7 +350,7 @@ async def __call__(self, scope, receive, send): # pylint: disable=too-many-bra
)
return
- expects_response = headers.get(b"x-uagents-connection") == b"sync"
+ expects_response = headers.get(b"x-uagents-connection") == b"sync" # type: ignore
if expects_response:
# Add a future that will be resolved once the query is answered
diff --git a/python/src/uagents/config.py b/python/src/uagents/config.py
index 0a8f9a7a..01985bd2 100644
--- a/python/src/uagents/config.py
+++ b/python/src/uagents/config.py
@@ -1,6 +1,10 @@
-from typing import Dict, List, Optional, Union
+import logging
+from typing import Dict, List, Literal, Optional, Union
+
+from pydantic import BaseModel
from uagents.types import AgentEndpoint
+from uagents.utils import get_logger
AGENT_PREFIX = "agent"
LEDGER_PREFIX = "fetch"
@@ -43,15 +47,53 @@
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]]],
+ agentverse: AgentverseConfig,
+ mailbox: bool = False,
+ proxy: bool = False,
+ logger: Optional[logging.Logger] = None,
) -> List[AgentEndpoint]:
"""
Parse the user-provided endpoint configuration.
+ Args:
+ endpoint (Optional[Union[str, List[str], Dict[str, dict]]]): The endpoint configuration.
+ agentverse (AgentverseConfig): The agentverse configuration.
+ mailbox (bool): Whether to use the mailbox endpoint.
+ proxy (bool): Whether to use the proxy endpoint.
+ logger (Optional[logging.Logger]): The logger to use.
+
Returns:
- Optional[List[Dict[str, Any]]]: The parsed endpoint configuration.
+ Optional[List[AgentEndpoint]: The parsed endpoint configuration.
"""
+
+ logger = logger or get_logger("config")
+
+ if endpoint:
+ if mailbox:
+ logger.warning("Endpoint configuration overrides mailbox setting.")
+ if proxy:
+ logger.warning("Endpoint configuration overrides proxy setting.")
+ elif mailbox and proxy:
+ logger.warning(
+ "Mailbox and proxy settings are mutually exclusive. "
+ "Defaulting to mailbox."
+ )
+
if isinstance(endpoint, dict):
endpoints = [
AgentEndpoint.model_validate(
@@ -65,6 +107,10 @@ def parse_endpoint_config(
]
elif isinstance(endpoint, str):
endpoints = [AgentEndpoint.model_validate({"url": endpoint, "weight": 1})]
+ elif mailbox:
+ endpoints = [AgentEndpoint(url=f"{agentverse.url}/v1/submit", weight=1)]
+ elif proxy:
+ endpoints = [AgentEndpoint(url=f"{agentverse.url}/v1/proxy/submit", weight=1)]
else:
endpoints = []
return endpoints
@@ -72,36 +118,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,
+ )
diff --git a/python/src/uagents/mailbox.py b/python/src/uagents/mailbox.py
index 81ea6e8a..b41a04ee 100644
--- a/python/src/uagents/mailbox.py
+++ b/python/src/uagents/mailbox.py
@@ -1,101 +1,229 @@
import asyncio
-import json
import logging
+from datetime import datetime
from typing import Optional
import aiohttp
-import pydantic
-import websockets.exceptions
from aiohttp.client_exceptions import ClientConnectorError
-from websockets import connect
+from pydantic import UUID4, BaseModel, ValidationError
-from uagents.config import MAILBOX_POLL_INTERVAL_SECONDS
-from uagents.crypto import is_user_address
+from uagents.config import MAILBOX_POLL_INTERVAL_SECONDS, AgentType, AgentverseConfig
+from uagents.crypto import Identity, is_user_address
from uagents.dispatch import dispatcher
from uagents.envelope import Envelope
+from uagents.models import Model
+from uagents.types import AgentEndpoint
from uagents.utils import get_logger
+logger = get_logger("mailbox")
-class MailboxClient:
- """Client for interacting with the Agentverse mailbox server."""
- def __init__(self, agent, logger: Optional[logging.Logger] = None):
- self._agent = agent
- self._access_token: Optional[str] = None
- self._envelopes_to_delete = asyncio.Queue()
- self._poll_interval = MAILBOX_POLL_INTERVAL_SECONDS
- self._logger = logger or get_logger("mailbox")
+class AgentverseConnectRequest(Model):
+ user_token: str
+ agent_type: AgentType
- @property
- def base_url(self):
- """
- Property to access the base url of the mailbox server.
- Returns: The base url of the mailbox server.
+class ChallengeRequest(BaseModel):
+ address: str
- """
- return self._agent.mailbox["base_url"]
- @property
- def agent_mailbox_key(self):
- """
- Property to access the agent_mailbox_key of the mailbox server.
+class ChallengeResponse(BaseModel):
+ challenge: str
- Returns: The agent_mailbox_key of the mailbox server.
- """
- return self._agent.mailbox["agent_mailbox_key"]
- @property
- def protocol(self):
- """
- Property to access the protocol of the mailbox server.
+class ChallengeProof(BaseModel):
+ address: str
+ challenge: str
+ challenge_response: str
- Returns: The protocol of the mailbox server {ws, wss, http, https}.
- """
- return self._agent.mailbox["protocol"]
- @property
- def http_prefix(self):
- """
- Property to access the http prefix of the mailbox server.
+class ChallengeProofResponse(Model):
+ access_token: str
+ expiry: str
+
+
+class RegistrationRequest(BaseModel):
+ address: str
+ challenge: str
+ challenge_response: str
+ agent_type: AgentType
+ endpoints: Optional[list[AgentEndpoint]] = None
+
+
+class RegistrationResponse(Model):
+ success: bool
+
+
+class StoredEnvelope(BaseModel):
+ uuid: UUID4
+ envelope: Envelope
+ received_at: datetime
+ expires_at: datetime
+
+
+def is_mailbox_agent(
+ endpoints: list[AgentEndpoint], agentverse: AgentverseConfig
+) -> bool:
+ """
+ Check if the agent is a mailbox agent.
+
+ Returns:
+ bool: True if the agent is a mailbox agent, False otherwise.
+ """
+ return any([f"{agentverse.url}/v1/submit" in ep.url for ep in endpoints])
+
+
+def is_proxy_agent(
+ endpoints: list[AgentEndpoint], agentverse: AgentverseConfig
+) -> bool:
+ """
+ Check if the agent is a proxy agent.
+
+ Returns:
+ bool: True if the agent is a proxy agent, False otherwise.
+ """
+ return any([f"{agentverse.url}/v1/proxy/submit" in ep.url for ep in endpoints])
+
+
+async def register_in_agentverse(
+ request: AgentverseConnectRequest,
+ identity: Identity,
+ endpoints: list[AgentEndpoint],
+ agentverse: AgentverseConfig,
+) -> RegistrationResponse:
+ """
+ Registers agent in Agentverse
+
+ Args:
+ request (AgentverseConnectRequest): Request object
+ identity (Identity): Agent identity object
+ endpoints (list[AgentEndpoint]): Endpoints of the agent
+ agentverse (AgentverseConfig): Agentverse configuration
+
+ Returns:
+ RegistrationResponse: Registration
+ """
+ async with aiohttp.ClientSession() as session:
+ # get challenge
+ challenge_url = f"{agentverse.url}/v1/auth/challenge"
+ challenge_request = ChallengeRequest(address=identity.address)
+ logger.debug("Requesting mailbox access challenge")
+ async with session.post(
+ challenge_url,
+ data=challenge_request.model_dump_json(),
+ headers={
+ "content-type": "application/json",
+ "Authorization": f"Bearer {request.user_token}",
+ },
+ ) as resp:
+ resp.raise_for_status()
+ challenge = ChallengeResponse.model_validate_json(await resp.text())
+
+ # response to challenge with signature to get token
+ prove_url = f"{agentverse.url}/v1/agents"
+ async with session.post(
+ prove_url,
+ data=RegistrationRequest(
+ address=identity.address,
+ challenge=challenge.challenge,
+ challenge_response=identity.sign(challenge.challenge.encode()),
+ endpoints=endpoints,
+ agent_type=request.agent_type,
+ ).model_dump_json(),
+ headers={
+ "content-type": "application/json",
+ "Authorization": f"Bearer {request.user_token}",
+ },
+ ) as resp:
+ if resp.status == 409:
+ logger.exception("Agent is already registered in Agentverse.")
+ return RegistrationResponse(success=False)
+ resp.raise_for_status()
+ registration_response = RegistrationResponse.parse_raw(await resp.text())
+ if registration_response.success:
+ logger.info(
+ f"Successfully registered as {request.agent_type} agent in Agentverse"
+ )
+
+ if request.agent_type == "mailbox" and not is_mailbox_agent(endpoints, agentverse):
+ logger.exception(
+ f"Agent endpoints {endpoints} do not match registered agent type: {request.agent_type}"
+ f"Please restart agent with endpoint='{agentverse.url}/v1/submit'"
+ )
+
+ return registration_response
- Returns: The http prefix of the mailbox server {http, https}.
- """
- return self._agent.mailbox["http_prefix"]
+
+class MailboxClient:
+ """Client for interacting with the Agentverse mailbox server."""
+
+ def __init__(
+ self,
+ identity: Identity,
+ agentverse: AgentverseConfig,
+ logger: Optional[logging.Logger] = None,
+ ):
+ self._identity = identity
+ self._agentverse = agentverse
+ self._access_token: Optional[str] = None
+ self._poll_interval = MAILBOX_POLL_INTERVAL_SECONDS
+ self._logger = logger or get_logger("mailbox")
async def run(self):
"""
Runs the mailbox client.
"""
+ self._logger.info(f"Starting mailbox client for {self._agentverse.url}")
loop = asyncio.get_event_loop()
- loop.create_task(self.start_polling())
- loop.create_task(self.process_deletion_queue())
+ loop.create_task(self._check_mailbox_loop())
- async def start_polling(self):
+ async def _check_mailbox_loop(self):
"""
- Runs the mailbox client. Acquires an access token if needed and then starts a polling loop.
+ Retrieves envelopes from the mailbox server and processes them.
"""
- self._logger.info(f"Connecting to mailbox server at {self.base_url}")
while True:
try:
- if self._access_token is None:
- await self._get_access_token()
- if self.protocol in {"ws", "wss"}:
- await self._open_websocket_connection()
- else:
- await self._poll_server()
- await asyncio.sleep(self._poll_interval)
- except ClientConnectorError:
- self._logger.exception("Failed to connect to mailbox server")
+ async with aiohttp.ClientSession() as session:
+ if self._access_token is None:
+ await self._get_access_token()
+ mailbox_url = f"{self._agentverse.url}/v1/mailbox"
+ async with session.get(
+ mailbox_url,
+ headers={
+ "Authorization": f"token {self._access_token}",
+ },
+ ) as resp:
+ success = resp.status == 200
+ if success:
+ items = (await resp.json())["items"]
+ for item in items:
+ stored_env = StoredEnvelope.model_validate(item)
+ await self._handle_envelope(stored_env)
+ elif resp.status == 401:
+ self._access_token = None
+ self._logger.warning(
+ "Access token expired: a new one should be retrieved automatically"
+ )
+ else:
+ self._logger.exception(
+ f"Failed to retrieve messages: {resp.status}:{(await resp.text())}"
+ )
+ except ClientConnectorError as ex:
+ self._logger.warning(f"Failed to connect to mailbox server: {ex}")
+
+ await asyncio.sleep(self._poll_interval)
- async def _handle_envelope(self, payload: dict):
+ async def _handle_envelope(self, stored_env: StoredEnvelope):
"""
Handles an envelope received from the mailbox server.
Dispatches the incoming messages and adds the envelope to the deletion queue.
+
+ Args:
+ stored_env (StoredEnvelope): Envelope to handle
"""
try:
- env = Envelope.model_validate(payload["envelope"])
- except pydantic.ValidationError:
+ env = Envelope.model_validate(stored_env.envelope)
+ except ValidationError:
self._logger.warning("Received invalid envelope")
return
@@ -120,85 +248,34 @@ async def _handle_envelope(self, payload: dict):
env.session,
)
- # queue envelope for deletion from server
- await self._envelopes_to_delete.put(payload)
+ # delete envelope from server
+ await self._delete_envelope(stored_env.uuid)
- async def process_deletion_queue(self):
- """
- Processes the deletion queue. Deletes envelopes from the mailbox server.
+ async def _delete_envelope(self, uuid: UUID4):
"""
- async with aiohttp.ClientSession() as session:
- while True:
- try:
- env = await self._envelopes_to_delete.get()
- env_url = (
- f"{self.http_prefix}://{self.base_url}/v1/mailbox/{env['uuid']}"
- )
- self._logger.debug(f"Deleting message: {env}")
- async with session.delete(
- env_url,
- headers={"Authorization": f"token {self._access_token}"},
- ) as resp:
- if resp.status != 200:
- self._logger.exception(
- f"Failed to delete envelope from inbox: {(await resp.text())}"
- )
- except ClientConnectorError as ex:
- self._logger.warning(f"Failed to connect to mailbox server: {ex}")
- except Exception as ex:
- self._logger.exception(
- f"Got exception while processing deletion queue: {ex}"
- )
-
- async def _poll_server(self):
- """
- Polls the mailbox server for envelopes and handles them.
- """
- async with aiohttp.ClientSession() as session:
- # check the inbox for envelopes and handle them
- mailbox_url = f"{self.http_prefix}://{self.base_url}/v1/mailbox"
- async with session.get(
- mailbox_url,
- headers={"Authorization": f"token {self._access_token}"},
- ) as resp:
- success = resp.status == 200
- if success:
- items = (await resp.json())["items"]
- for item in items:
- await self._handle_envelope(item)
- elif resp.status == 401:
- self._access_token = None
- self._logger.warning(
- "Access token expired: a new one should be retrieved automatically"
- )
- else:
- self._logger.exception(
- f"Failed to retrieve messages: {resp.status}:{(await resp.text())}"
- )
+ Deletes envelope from the mailbox server.
- async def _open_websocket_connection(self):
- """
- Opens a websocket connection to the mailbox server and handles incoming envelopes.
+ Args:
+ uuid (UUID4): UUID of the envelope to delete
"""
try:
- async with connect(
- f"{self.protocol}://{self.base_url}/v1/events?token={self._access_token}"
- ) as websocket:
- # wait for the event stream to come in
- while True:
- msg = await websocket.recv()
- msg = json.loads(msg)
- if msg["type"] == "envelope":
- self._logger.debug(f"Got envelope: {msg['payload']}")
- await self._handle_envelope(msg["payload"])
-
- except websockets.exceptions.ConnectionClosedError:
- self._logger.warning("Mailbox connection closed")
- self._access_token = None
-
- except ConnectionRefusedError:
- self._logger.warning("Mailbox connection refused")
- self._access_token = None
+ async with aiohttp.ClientSession() as session:
+ env_url = f"{self._agentverse.url}/v1/mailbox/{str(uuid)}"
+ self._logger.debug(f"Deleting message: {str(uuid)}")
+ async with session.delete(
+ env_url,
+ headers={
+ "Authorization": f"token {self._access_token}",
+ },
+ ) as resp:
+ if resp.status != 200:
+ self._logger.exception(
+ f"Failed to delete envelope from inbox: {(await resp.text())}"
+ )
+ except ClientConnectorError as ex:
+ self._logger.warning(f"Failed to connect to mailbox server: {ex}")
+ except Exception as ex:
+ self._logger.exception(f"Got exception while deleting message: {ex}")
async def _get_access_token(self):
"""
@@ -206,10 +283,11 @@ async def _get_access_token(self):
"""
async with aiohttp.ClientSession() as session:
# get challenge
- challenge_url = f"{self.http_prefix}://{self.base_url}/v1/auth/challenge"
+ challenge_url = f"{self._agentverse.url}/v1/auth/challenge"
+ challenge_request = ChallengeRequest(address=self._identity.address)
async with session.post(
challenge_url,
- data=json.dumps({"address": self._agent.address}),
+ data=challenge_request.model_dump_json(),
headers={"content-type": "application/json"},
) as resp:
if resp and resp.status == 200:
@@ -221,22 +299,23 @@ async def _get_access_token(self):
return
# response to challenge with signature to get token
- prove_url = f"{self.http_prefix}://{self.base_url}/v1/auth/prove"
+ prove_url = f"{self._agentverse.url}/v1/auth/prove"
+ proof_request = ChallengeProof(
+ address=self._identity.address,
+ challenge=challenge,
+ challenge_response=self._identity.sign(challenge.encode()),
+ )
async with session.post(
prove_url,
- data=json.dumps(
- {
- "address": self._agent.address,
- "agent_mailbox_key": self.agent_mailbox_key,
- "challenge": challenge,
- "challenge_response": self._agent.sign(challenge.encode()),
- }
- ),
+ data=proof_request.model_dump_json(),
headers={"content-type": "application/json"},
) as resp:
if resp and resp.status == 200:
+ challenge_proof_response = ChallengeProofResponse.parse_raw(
+ await resp.text()
+ )
self._logger.info("Mailbox access token acquired")
- self._access_token = (await resp.json())["access_token"]
+ self._access_token = challenge_proof_response.access_token
else:
self._logger.exception(
f"Failed to prove authorization: {(await resp.text())}"
diff --git a/python/tests/test_config.py b/python/tests/test_config.py
index c3760788..a3b865ea 100644
--- a/python/tests/test_config.py
+++ b/python/tests/test_config.py
@@ -1,111 +1,103 @@
import unittest
from uagents import Agent
+from uagents.types import AgentEndpoint
+
+MAILBOX_ENDPOINT = "https://agentverse.ai/v1/submit"
+PROXY_ENDPOINT = "https://agentverse.ai/v1/proxy/submit"
+
agents = [
Agent(),
- Agent(mailbox="agent_mailbox_key@some_url"),
- Agent(mailbox={"agent_mailbox_key": "agent_mailbox_key", "base_url": "some_url"}),
- Agent(mailbox="agent_mailbox_key"),
- Agent(agentverse="agent_mailbox_key@some_url"),
- Agent(agentverse="agent_mailbox_key"),
- Agent(agentverse="http://some_url"),
- Agent(agentverse="wss://some_url"),
- Agent(agentverse="ws://some_url"),
- Agent(agentverse={"agent_mailbox_key": "agent_mailbox_key", "protocol": "wss"}),
+ Agent(mailbox="agent_mailbox_key"), # type: ignore (backwards compatibility)
+ Agent(agentverse="http://some_url", endpoint="http://some_url"),
Agent(agentverse="https://staging.agentverse.ai"),
Agent(agentverse={"base_url": "staging.agentverse.ai"}),
+ Agent(mailbox=True),
+ Agent(mailbox=True, endpoint="http://some_url"),
+ Agent(proxy=True),
+ Agent(proxy=True, endpoint="http://some_url"),
+ Agent(mailbox=True, proxy=True),
+]
+
+expected_endpoints = [
+ [],
+ [AgentEndpoint(url=MAILBOX_ENDPOINT, weight=1)],
+ [AgentEndpoint(url="http://some_url", weight=1)],
+ [],
+ [],
+ [AgentEndpoint(url=MAILBOX_ENDPOINT, weight=1)],
+ [AgentEndpoint(url="http://some_url", weight=1)],
+ [AgentEndpoint(url=PROXY_ENDPOINT, weight=1)],
+ [AgentEndpoint(url="http://some_url", weight=1)],
+ [AgentEndpoint(url=MAILBOX_ENDPOINT, weight=1)],
]
expected_configs = [
{
- "agent_mailbox_key": None,
"base_url": "agentverse.ai",
"protocol": "https",
"http_prefix": "https",
- "use_mailbox": False,
},
{
- "agent_mailbox_key": "agent_mailbox_key",
- "base_url": "some_url",
+ "base_url": "agentverse.ai",
"protocol": "https",
"http_prefix": "https",
- "use_mailbox": True,
},
{
- "agent_mailbox_key": "agent_mailbox_key",
"base_url": "some_url",
- "protocol": "https",
- "http_prefix": "https",
- "use_mailbox": True,
+ "protocol": "http",
+ "http_prefix": "http",
},
{
- "agent_mailbox_key": "agent_mailbox_key",
- "base_url": "agentverse.ai",
+ "base_url": "staging.agentverse.ai",
"protocol": "https",
"http_prefix": "https",
- "use_mailbox": True,
},
{
- "agent_mailbox_key": "agent_mailbox_key",
- "base_url": "some_url",
+ "base_url": "staging.agentverse.ai",
"protocol": "https",
"http_prefix": "https",
- "use_mailbox": True,
},
{
- "agent_mailbox_key": "agent_mailbox_key",
"base_url": "agentverse.ai",
"protocol": "https",
"http_prefix": "https",
- "use_mailbox": True,
},
{
- "agent_mailbox_key": None,
- "base_url": "some_url",
- "protocol": "http",
- "http_prefix": "http",
- "use_mailbox": False,
- },
- {
- "agent_mailbox_key": None,
- "base_url": "some_url",
- "protocol": "wss",
+ "base_url": "agentverse.ai",
+ "protocol": "https",
"http_prefix": "https",
- "use_mailbox": False,
},
{
- "agent_mailbox_key": None,
- "base_url": "some_url",
- "protocol": "ws",
- "http_prefix": "http",
- "use_mailbox": False,
- },
- {
- "agent_mailbox_key": "agent_mailbox_key",
"base_url": "agentverse.ai",
- "protocol": "wss",
+ "protocol": "https",
"http_prefix": "https",
- "use_mailbox": True,
},
{
- "agent_mailbox_key": None,
- "base_url": "staging.agentverse.ai",
+ "base_url": "agentverse.ai",
"protocol": "https",
"http_prefix": "https",
- "use_mailbox": False,
},
{
- "agent_mailbox_key": None,
- "base_url": "staging.agentverse.ai",
+ "base_url": "agentverse.ai",
"protocol": "https",
"http_prefix": "https",
- "use_mailbox": False,
},
]
class TestConfig(unittest.TestCase):
def test_parse_agentverse_config(self):
- for agent, expected_config in zip(agents, expected_configs): # noqa
- self.assertEqual(agent.agentverse, expected_config)
+ for agent, expected_config, endpoints, index in zip( # noqa
+ agents,
+ expected_configs,
+ expected_endpoints,
+ range(len(agents)),
+ ):
+ self.assertEqual(
+ agent.agentverse.model_dump(),
+ expected_config,
+ f"Failed on agent {index}",
+ )
+ self.assertEqual(agent._endpoints, endpoints, f"Failed on agent {index}")